一、Java编程思想之对象入门
前言
重写梳理一下Java基础知识,结合着《Java编程思想》,思考为什么要这样?
1、面向对象程序设计
(1) 所有东西都是对象。可将对象想象成一种新型变量;它保存着数据,但可要求它对自身进行操作。
(2) 程序是一大堆对象的组合;通过消息传递,各对象知道自己该做些什么。
(3) 每个对象都有自己的存储空间,可容纳其他对象。或者说,通过封装现有对象,可制作出新型对象。
(4) 每个对象都有一种类型。一个类最重要的特征就是“能将什么消息发给它?”。
(5) 同一类所有对象都能接收相同的消息。
2、接口的由来----接收请求
如何利用对象完成真正有用的工作呢?必须有一种办法能向对象发出请求,令其做一些实际的事情,比如完成一次交易、在屏幕上画一些东西或者打开一个开关等等。
每个对象仅能接受特定的请求。我们向对象发出的请求是通过它的“接口”(Interface)定义的,对象的“类型”或“类”则规定了它的接口形式。
3、public,private,protected的由来----实现方案的隐藏
从根本上说,大致有两方面的人员涉足面向对象的编程:“类创建者”(创建新数据类型的人)以及“客户程序员”。对类创建者来说,他们的目标则是从头构建一个类,只向客户程序员开放有必要开放的东西(接口),其他所有细节都隐藏起来。因此便有了public,private,protected 以及暗示性的friendly。
4、成员对象的由来----方案的重复使用
新类可由任意数量和类型的其他对象构成。这个概念叫作“组织”——在现有类的基础上组织一个新类。对象的组织具有极大的灵活性。新类的“成员对象”通常设为“私有”(Private),使用这个类的客户程序员不能访问它们。这样一来,我们可在不干扰客户代码的前提下,从容地修改那些成员。也可以在“运行期”更改成员,这进一步增大了灵活性。
后面要讲到的“继承”并不具备这种灵活性。因此新建类的时候,首先应考虑“组织”对象;这样做显得更加简单和灵活。
5、继承的由来----重新使用接口
多种数据类型,令其实现大致相同的功能,代码量大且臃肿。但若能利用现成的数据类型,对其进行“克隆”,再根据情况进行添加和修改就好了。“继承”正是针对这个目标而设计的。
公共父类:
public class Animal {
private String name;
private int id;
public Animal(String myName, int myid) {
name = myName;
id = myid;
}
public void eat(){
System.out.println(name+"正在吃");
}
public void sleep(){
System.out.println(name+"正在睡");
}
public void introduction() {
System.out.println("大家好!我是" + id + "号" + name + ".");
}
}
企鹅类:
public class Penguin extends Animal {
public Penguin(String myName, int myid) {
super(myName, myid);
}
}
老鼠类:
public class Mouse extends Animal {
public Mouse(String myName, int myid) {
super(myName, myid);
}
}
5.1、继承特点:
(1)子类拥有父类非 private 的属性、方法。
(2)子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
(3)子类可以用自己的方式实现父类的方法。
(4)类的继承是单一继承。
(5)提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)
5.2、继承关键字
5.2.1、extends与 implements关键字
extends 只能继承一个类,类的继承是单一继承。
implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口。
5.2.2、super 与 this 关键字
super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
this关键字:指向自己的引用。
5.3.3、final关键字
final 关键字声明类可以把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写。
注:实例变量也可以被定义为 final,被定义为 final 的变量不能被修改。被声明为 final 类的方法自动地声明为 final,但是实例变量并不是 final。
5.3、重写和重载
5.4、上溯造型和下塑造型
方法的重写、重载与动态连接构成多态性。Java之所以引入多态的概念,原因之一是它在类的继承问题上和C++不同,后者允许多继承,这确实给其带来的非常强大的功能,但是复杂的继承关系也给C++开发者带来了更大的麻烦,为了规避风险,Java只允许单继承。
我定义了一个子类Cat,它继承了Animal类。我可以通过 Cat c = new Cat(); 实例化一个Cat的对象。但当我这样定义时: Animal a = new Cat(); 这代表什么意思呢?
它表示我定义了一个Animal类型的引用,指向新建的Cat类型的对象。那么这样做有什么意义呢?
因为子类是对父类的一个改进和扩充,所以一般子类在功能上较父类更强大, 定义一个父类类型的引用指向一个子类的对象既可以使用子类强大的功能,又可以抽取父类的共性。
所以,父类类型的引用可以调用父类中定义的所有属性和方法,而对于子类中定义而父类中没有的方法,它是无可奈何的; 对于父类中定义的方法,如果子类中重写了该方法,那么父类类型的引用将会调用子类中的这个方法,这就是动态连接。
public class Test {
public static void main(String[] args) {
show(new Cat()); // 以 Cat 对象调用 show 方法
show(new Dog()); // 以 Dog 对象调用 show 方法
Animal a = new Cat(); // 向上转型
a.eat(); // 调用的是 Cat 的 eat
Cat c = (Cat)a; // 向下转型
c.work(); // 调用的是 Cat 的 work
}
public static void show(Animal a) {
a.eat();
// 类型判断
if (a instanceof Cat) { // 猫做的事情
Cat c = (Cat)a;
c.work();
} else if (a instanceof Dog) { // 狗做的事情
Dog c = (Dog)a;
c.work();
}
}
}
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void work() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void work() {
System.out.println("看家");
}
}
程序输出:
吃鱼
抓老鼠
吃骨头
看家
吃鱼
抓老鼠
下面我们看另外一个例子:
/**
* @author WangQun
* 动物抽象类
*/
abstract class Animal {
public abstract void speak();
public void eat(){
// 闷头吃,不做额外的事情
}
}
/**
* @author WangQun
* 门神接口
*/
interface DoorGod {
void guard();
}
/**
* @author WangQun
* 猫,继承自动物
*/
class Cat extends Animal {
@Override
public void eat() {
try {
Thread.sleep( 1000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
super .eat();
}
@Override
public void speak() {
System.out.println( " 喵喵 " );
}
}
/**
* @author WangQun
* 狗,继承自动物,实现门神接口
*/
class Dog extends Animal implements DoorGod{
@Override
public void speak() {
System.out.println( " 汪汪 " );
}
public void guard() {
while ( true ){
System.out.println( " 汪汪 " );
}
}
}
其中Animal为基类,定义speak和eat方法,eat方法给出了空实现; DoorGod为门神接口,定义了 guard方法来守护家门; Cat为继承Animal的子类,这里假定猫有挑食的习惯,在eat中要耽搁点时间看看伙食;Dog也为继承Animal的子类,同时它实现了DoorGod接口来守护家门。
先说说上溯造型(upcasting)。这个术语缘于继承关系图的传统画法:将基类至于顶部,而向下发展的就是派生类。根据上面的sample,我给出下面的一个小应用:
public class Main {
/**
* @param animal
* 上溯,传入的参数强制转换成其父类
*/
public static void upcasting(Animal animal){
animal.speak();
animal.eat();
}
public static void main(String[] args) {
Animal dog1 = new Dog();
upcasting(dog1);
Dog dog2 = new Dog();
upcasting(dog2);
}
}
由于upcasting(Animal animal)方法的参数是 Animal类型的,因此如果传入的参数是 Animal的子类,传入的参数就会被转换成父类Animal类型,这样你创建的Dog对象能使用的方法只是Animal中的签名方法;也就是说,在上溯的过程中,Dog的接口变窄了,它本身的一些方法(例如实现了 DoorGod的guard方法)就不可见了。如果你想使用Dog中存在而Animal中不存在的方法(比如guard方法),编译时不能通过的。由此可见,上溯造型是安全的类型转换。另一方面,虽然upcasting(Animal animal)方法的参数是 Animal类型,但传入的参数可以是Animal的派生类(这也是OO编程中惯用的编程方法),这里面就有个对象的类型识别问题,也就是运行时类型识别(run-time type identification,缩写为RTTI) ,这也可以单独写一篇文章了,《Thinking in Java》中的第10章详细地阐述了RTTI。
相对于类型转换安全的上溯造型,下溯造型就未必是安全的了。我们经常会做些强制类型转换的事情,有时我们也会无意间遇到 ClassCastException的转换异常(从这一点来说,我们应该多用范型来避免不安全的类型转换)。例如:
public static void downcasting(Animal animal){
//判断类是不是实现自哪个接口
if(animal instanceof DoorGod){
DoorGod doorGod = (DoorGod)animal;
doorGod.guard();
}
if(animal instanceof Cat){
Cat cat = (Cat)animal;
cat.speak();
}
}
如果没有采取措施(上面使用的措施是instanceof)判断对象的类型,那么向下的强制转换就是不安全的。这种转换错误在编译时是不能检测出来的,只有在运行时才会抛出 ClassCastException异常,对于测试来说,这样的错误也是很难检测的。
6、抽象类的由来----限制基础类只为自己的衍生类提供一个接口,不允许创建一个自己的实例
7、对象的创建和存在时间----对象需要的数据位于哪儿,如何控制对象的“存在时间”呢?
一种是编写程序确定存储及存在时间。如:C++认为程序的执行效率是最重要的一个问题。为获得最快的运行速度,存储以及存在时间可在编写程序时决定。这样便为存储空间的分配和释放提供了一个优先级。这种优先级的控制是非常有价值的。然而,我们同时也牺牲了灵活性,因为在编写程序时,必须知道对象的准确的数量、存在时间、以及类型。
另一种内存池中动态创建对象。若需一个新对象,只需在需要它的时候在内存堆里简单地创建它即可。由于存储空间的管理是运行期间动态进行的,所以在内存堆里分配存储空间的时间比在堆栈里创建的时间长得多(在堆栈里创建存储空间一般只需要一个简单的指令,将堆栈指针向下或向下移动即可)。除此以外,更大的灵活性对于常规编程问题的解决是至关重要的。
程序员可用两种方法来破坏一个对象:用程序化的方式决定何时破坏对象,或者利用由运行环境提供的一种“垃圾收集器”特性,自动寻找那些不再使用的对象,并将其清除。当然,垃圾收集器显得方便得多,但要求所有应用程序都必须容忍垃圾收集器的存在,并能默许随垃圾收集带来的额外开销。
7.1集合与继承器----不知道需要对象个数、存活时间,如何保存呢?
会自动扩充的集合对象,容纳指向其他对象的地址。
最后
持续更新中…