Java—面向对象(中)
1.对象的使用
我们在上一篇面向对象上中已经提到了对象的创建和使用,以及对内存进行了分析
需要注意的是:
- 必需使用new关键字创建对象
- 使用对象引用.成员变量来引用对象的成员变量
- 使用对象引用.方法(参数列表)来调用对象的方法
- 同一类的每个对象有不同的成员变量存储空间
- 同一类的每个对象共享该类方法(非静态方法是针对每个对象进行调用)
2.关键字的使用
1.this关键字
在类的方法定义中使用的this关键字代表使用该方法的对象的引用。当必需指出当前使用方法的对象是谁时要使用this。有时使用this可以处理方法中成员变量和参数重名的情况(当成员变量和局部变量重名的时候在方法中使用this表示该方法所在类的成员变量)。this可以看作是一个变量,他的值是当前对象的引用
以下代码就是在构造方法中使用了this关键字:
public class Test{ private int i = 0; Test(int i){ this.i = i; } public static void main(String[] args){ Test t = new Test(10); System.ou.println(t.i); } } //输出:10
2.static关键字
static是静态的意思,在Java类中用static声明的成员变量为静态成员变量,他为该类的公用变量,在第一次使用时被初始化,对于该类的所有对象来说,static成员变量只有一份,由于是公共变量,所以必须要特别注意变量在内存中值的变化
用static声明的方法为静态方法,在调用该方法时,不会将对象的引用传递给它,所以static方法中不可访问非static的成员。(静态方法不再是针对某个对象调用,所以不能访问非静态成员)
可以通过对象引用或类名(不需要实例化)访问静态成员(类名.静态对象)
public class Test{ private static int i =0; private String name = null; int a ; Test(String name){ this.name = name; a = i++; //注意++在后,先赋值再自加1 } public static void main(String[] args){ Test.i = 10; Test t = new Test("t"); System.out.println(t.name+":"+t.a); Test t1 = new Test("t1"); System.out.println(t1.name+":"+t1.a); } } //输出: //t:10 //t1:11
3.import和package
为了管理大型软件系统中数目众多的类,解决类的命名冲突问题,java引入包(package)机制,提供类的多重命名空间(我们在基本语法中提到过包相当于文件夹)
package语句作为java源文件的第一条语句,表示该Java源文件在该包下。(若缺省该语句,则指定为无名包)
它的格式是:package pkg1[.pkg2[.pkg3…]];
java编译器把包对应于文件系统的目录管理,package语句中,用”.”来表示包的层次例如:package com.jian; //该文件中所有的类位于.comjian目录下
注意:
- 如果将一个类打包,则使用该类时,必需使用该类的全名(例如:com.jian.Myclass),jav编译器才会找到该类
- 也可以使用import在文件的开头引入要使用的类;例如:import com.jian.MyClass
- 可以不需要用import语句直接使用java.lang包中的类
总结:
1.如果想将一个类放入包中,在这个类源文件第一句话写package
2.必需保证该类的源文件位于正确的目录下
该类的源码可能会产生影响
3.另外的类想访问的话
写全名
引入
*或者写具体类名
访问位于同一个包中的类不需要引入
4.必需class文件的最上层包的父母路位于classPath下(涉及到环境变量问题)
5.在cmd中执行一个类的时候也是需要写全包名的
JDK主要的包:
-
- 可以使用jar –cvf xx.jar *.*命令将自己的java文件(.class)打包成jar文件(可以使用jar -help来查看该命令详细参数)
- 可以将整个jar包作为classPath设置到环境变量中这样就可以直接在cmd中进行调用
3.封装
1.封装概述
Java中封装的实质就是将类的状态信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问。
2.访问控制
java权限修饰符public、protected 、private置于类的成员定义前,用来限定其他对象对该类对象成员的访问权限
对于class的权限修饰只可以用public 和default(default是默认的,什么都不写就是default)
public修饰的类可以在任意地方被访问
default修饰的类只可以被同一个包内部的类访问
3.封装步骤
由上面访问控制图可以看出,封装第一步的关键就是修饰符private的使用
1.修改属性的可见性
将类中的属性由public修改为private
关键代码:
public class Test{ private String name; //姓名 private String gender; //性别 private int age; //年龄 }
分析:
将成员变量(属性)修改为private后,其他类就无法访问了,如果访问则需要进行封装的第二步。
2.设置访问或操作的方法
为类的私有变量添加操作的方法。
关键代码:
public class Test{ private String name; //姓名 private String gender; //性别 private int age; //年龄 public String getName(){ return name; } public int getGender(){ return gender; } public String getAge(){ return age; } public void setName(String name){ this.name = name; } public void setGender(String gender){ this.gender = gender; } public void setAge(int age){ this.age = age; } }
3.设置操作封装属性的限制条件
此时我们能通过方法取操作封装的属性了,但是还没有对属性值设置合法性检查,需要给set的操作方法中利用条件判断语句进行赋值限制。
public class Test{ private String name; //姓名 private String gender; //性别 private int age; //年龄 public String getName(){ return name; } public int getGender(){ return gender; } public String getAge(){ return age; } public void setName(String name){ this.name = name; } public void setGender(String gender){ if(gender.equals("男") || gender.equals("女")){ this.gender = gender; } } public void setAge(int age){ if(age > 0 && age < 150){ this.age = age; } else{ System.out.println("你的输入有误!"); return; } } }
4.方法重载
1.什么是重载
就是在同一类中方法名相同,方法参数的个数和类型不同(参数列表不同),通过个数和类型的不同来区分不同的函数(方法);
方法的重载跟返回值类型和修饰符无关,Java的重载是发生在本类中的,重载的条件实在本类中有多个方法名相同,
但参数列表不同(可能是,参数个数不同或者参数类型不同)跟返回值无关;
2.举例
//定义类 class Test{ private int i = 0; //默认的构造函数 T1(){ } //重载一个构造方法 T1(int i){ this.i = i; } } //测试类 class T1{ public static void main(String[] args) { Test t1 = new Test();//调用的是无参构造方法,即Test()这个构造方法 Test t2 = new Test(20);//调用的是有参数的构造方法,即Test(int i)这个构造方法 } }
//测试类 class T1{ public static void main(String[] args) { int sum = add(2,3);//调用的是第一个方法 int num = add(2,3,5);//调用的是第二个方法 } //方法重载1 public static int add(int a,int b) { return a+b; } //方法重载2 public static int add(int a,int b,int c) { return a+b+c; } } //sum的值为5;num的值为10
5.继承
1.概述
继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
生活中的继承:
兔子和羊属于食草动物类,狮子和豹属于食肉动物类。
食草动物和食肉动物又是属于动物类。
所以继承需要符合的关系是:is-a,父类更通用,子类更具体。
虽然食草动物和食肉动物都是属于动物,但是两者的属性和行为上有差别,所以子类会具有父类的一般特性也会具有自身的特性。
2.类的继承格式
在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:
3.继承中构造方法(super关键字)
-
- Java规定:构造子类之前必须先构造父类
- 子类的构造方法中必须通过super关键字调用父类的构造方法,这样可以妥善的初始化继承自父类的成员变量
- 如果子类的构造方法中没有调用父类的构造方法,Java编译器会自动的加入对父类无参构造方法的调用(如果该父类没有无参的构造方法,则会出现编译错误)
- super() 调父类构造必须位于子类构造的第一句
- super指代当前对象的父类对象
super的用法:
-
- super.成员变量名:访问父类的成员变量
- super.方法名():调用父类的方法
- super():调用父类的构造方法
例子1:
//父类 public class Person { String name; char gender; } //子类 public class Student extends Person { // super (); //编译错误,必须位于子类构造方法的第一句 double score; Student(double score){ super(); //编译器默认会自动加上 this.score = score; super.name = "Tom"; } }
例子2:
//父类 public class Person { String name; char gender; Person(String name,char gender){ this.name = name; this.gender = gender; } } //子类 public class Student extends Person { // super (); //编译错误,必须位于子类构造方法的第一句 double score; Student(String name,char gender,double score){ // super(); //编译错误,父类中没有无参构造 super(name,gender); //显式调用父类有参构造 this.score = score; super.name = "Tom"; } }
注意:子类在构造过程中必须调用父类构造函数(如果没写,默认调用父类无参构造方法)
//首先定义一个父类 class FatherClass { private int n; FatherClass(){ System.out.println("FatherClass()"); } FatherClass(int n){ System.out.println("FatherClass("+n+")"); this.n = n; } }
//继承父类 class subClass extends FatherClass { private int n; subClass(int n) { System.out.println("FatherClass("+n+")"); this.n = n; } subClass() { super(300); System.out.println("FatherClass()"); } } //主方法 public class ChildClass { public static void main(String arg[]) { subClass sc1 = new subClass(); subClass sc2 = new subClass(400); } }
运行结果:
sc1很容易理解;sc2为什么会先输出一个FatherClass()呢?因为“如果子类的构造方法中没有显示调用基类构造方法,则系统默认调用基类无参数的构造方法”
4.方法的重写
- 在子类中可以根据需要对从基类(父类)中继承来的方法进行重写
- 重写方法必需和被重写方法具有相同方法名称、参数列表和返回类型
- 重写方法不能使用比被重写方法更严格的访问权限(即访问权限不能更低)
- 子类重写(即覆盖)继承自父类的方法,所以方法名、返回类型和参数列表与父类的方法相同,但方法的实现不同(方法体不同)
- 因为重写会覆盖父类方法,当重写方法被调用时,看对象的类型:当子类对象的重写方法被调用时(无论是通过子类的引用调用还是通过父类的引用调用),运行的是子类的重写后的版本
- 子类在重写父类的方法时,可以通过super关键字调用父类的版本,这样的语法通常用于子类的重写方法在父类方法的基础之上进行功能扩展。
看一个例子:
//测试类 public class Student { public static void main(String[] args) { Goo o = new Goo(); o.f(); Foo oo = new Goo(); oo.f(); } } //父类 class Foo{ public void f(){ System.out.println("Foo.f()"); } } //子类 class Goo extends Foo{ public void f(){ System.out.println("Goo.f()"); } }
//当子类对象的重写方法被调用时(无论通过子类的引用还是通过父类的引用),运行的都是子类重写后的方法。
//输出结果: //Goo.f() //Goo.f()
重写父类方法并扩展功能:
public class Student { public static void main(String[] args) { Goo o = new Goo(); o.f(); Foo oo = new Goo(); oo.f(); } } class Foo{ public void f(){ System.out.println("Foo.f()"); } } class Goo extends Foo{ public void f(){ super.f(); //调用父类的方法 System.out.println("Goo.f()"); } } //子类重写方法中的super.f(); 调用了父类的版本,这样的语法通常用于子类的重写方法再父类方法的基础之上进行功能扩展。 /* 运行结果: Foo.f() Goo.f() Foo.f() Goo.f() */
5.重写与重载的区别
- 重写(Override):1.发生在父子类(继承)中,(方法签名相同)方法名称相同,参数列表相同,方法体不同 2.遵循“运行期”绑定,重写方法被调用时,看对象的类型
- 重载(Overload):1.发生在同一类中,方法名称相同,参数列表不同,方法体不同 2.遵循“编译期”绑定,看引用的类型绑定方法