第6章 面向对象程序设计
6.1面向对象技术基础
面向对象三大特征:封装性, 继承性,多态性
封装性:1. 把属性和方法都放在一起 2.实现信息隐藏
类和对象的关系:类是对象的抽象描述。对象是类的实例。
一个类中包含属性和方法。
在Java语言中,编译器为所有的Java程序自动导入包“java.lang”,因此Java程序可以直接用“java.lang”中的类和接口。位于类继承关系层次结构树的根部的类Object就是在包“java.lang”中的一个类。
类前的访问限定符
public:1.入口类:类名必须和文件名相同;【注意】一个java源文件中只能有一个入口类。
2.公有类:此类可以被跨包访问。
default:默认(即class关键字前什么都不写),此类只能在本包中使用。
abstract: 抽象类
final: 最终类,此类不能被其它类继承。
成员变量(属性)前的限定符
public: 公共的,在所有类中都可以使用
default: 只有本包中的类可以使用
protected: 本包中的类和其他包中此类的子类都可以使用
private: 只在当前类中使用
【注意】一般情况下,属性的访问限定符用private(私有),实现信息隐藏。
final: 定义属性为常量,只能初始化一次,以后其值不能被修改。
static:静态变量,静态属性(类属性): 此属性被所有对象共享,在内存中只分配一次空间。
【使用方式】:对象名.属性名 或 类名.属性名
【注意】对于 非静态变量(即实例属性):每个对象都各自分配空间、赋值。
transient:暂时性变量,对象序列化
volatile:用于并发线程共享(了解)
成员方法的修饰符:
public: 所有类可以使用
default: 本包中使用
protected: 本包中和其他包中此类的子类可以使用
private: 在当前类中使用
【注意】:一般情况下,成员方法的访问限定符用public(公共的),供外部使用
final: 最终方法,此方法不能被子类重写(覆盖),但可重载
static: 静态方法(类方法) 【调用方式】:对象.方法名 或 类名.方法名
synchronized: 线程安全的
abstract: 抽象方法
native:此方法中可以使用其他如c/c++语言来实现一些底层操作
类的实例化
new 运算符:在堆内存上分配空间
构造方法:
构造方法在创建新的实例对象时起作用
构造方法通常用来初始化实例对象,例如: 初始化成员域或设置工作环境
可以含有多种构造方法(重载),但必须具有不同的参数列表
构造方法的特点:
与类同名
没有返回值,不能加void
通过new 关键字调用
默认的构造方法
n 如果没有显式地定义类的构造方法,则系统会为该类定义一个默认的构造方法。该构造方法不含任何参数。
格式为:类名( )
{ }
初始化的默认值: 基本数值类型: 0; boolean: false; 引用数据类型: null
最好自己写构造方法
一旦在类中定义了带参的构造方法,系统就不会再创建这个默认无参的构造方法了。所以如果程序中用到此构造方法的话,必须自己定义上。
【调用顺序】:先调用父类的构造方法,再调用自身的构造方法
继承性:extends : 子类可以直接使用父类的所有的非私有(private)的属性和方法
【继承的优点】:提高代码的复用率,精简代码
Java中只有单继承:即每个类只能有一个父类
this: 指当前对象
super: 指当前对象的父类。 用于调用父类的构造方法
【示例程序】
class A {
int a = 1;
double d = 2.0;
void show() {
System.out.println("Class A: a=" + a + "\td=" + d);
}
}
class B extends A {
float a = 3.0f;
String d = "Java program.";
void show() {
super.show();
System.out.println("Class B: a=" + a + "\td=" + d);
}
}
public class Test {
public static void main(String[]args) {
A a = new A();
a.show(); //输出:Class A: a=1 d=2.0
A b = new B(); //输出:Class A: a=1 d=2.0
// Class B: a=3.0 d=Java program.
b.show();
}
}
多态性:
静态多态(编译时多态):方法重载overload
动态多态(运行时多态):方法重写(覆盖)override (重写只存在于继承关系中)
方法重写:父类中非私有的方法,在子类中重新实现,并且方法名和父类中方法名必须一样,参数必须一样,返回值一样。
【构成重写的条件】:1.子类中方法名和父类中一样 2.参数必须一样 3.返回值一样 4.子类中方法的访问限定符的范围要大于等于父类中方法的访问限定符的范围。
【运行时多态发生的条件】:父类的引用指向子类的对象,通过父类的引用去调用父类被子类重写的方法的时候,发生动态多态。
【重载与重写练习】看这样一个题:
1) class Super{
2) public float getNum(){return 3.0f;}
3) }
4)
5) public class Sub extends Super{
6)
7) }
whichmethod, placed at line 6, will cause a compiler error?
A.public float getNum(){return 4.0f;} B. public void getNum(){}
C.public void getNum(double d){} D.public double getNum(float d){return 4.0d;}
Answer:B
A属于方法的重写,因为修饰符和参数列表都一样。
B出现编译错误,如下:
Sub.java:6:Sub 中的getNum() 无法覆盖Super 中的getNum();正在尝试使用不兼容的返回类型
找到: void
需要: float
public void getNum(){}
^
1 错误
B既不是重写也不是重载,重写需要相同的返回值类型和参数列表,访问修饰符的限制一定要大于或等于被重写方法的访问修饰符(public>protected>default>private);
【重载】:必须具有不同的参数列表;可以有不同的返回类型,只要参数列表不同就可以了;可以有不同的访问修饰符;
把其看做是重载,那么在java中是不能以返回值来区分重载方法的,所以b不对.
值传递与引用传递
值传递: 实参传值给形参,形参的改变不会影响实参、基本数据类型的参数传递,都是值传递
引用传递:因为引用中存的是地址,实参传给形参地址,形参的改变会影响实参
【注意特殊】String:虽然是引用类型,但作为参数传递时,采用的是值传递。
关键字:final
1) final 属性:这样的变量就是常量了。final的变量假如没有赋予初值的话,其他方法就必须给它赋值,但只能赋值一次,以后其值不能被修改。而且执行效率也比普通的变量要高。
2) final 方法:最终方法,此方法不能被子类重写(覆盖),但可重载。而且定义为final的方法执行效率高。
3) final 类:最终类,此类不能被继承。java.lang.String就是一个final类。
【总结】
final的意思是“最终的”,它所修饰的东西都是最终的、不可改变的,效率也比较高。通常在Java的优化编程中会提及这一点。
【习题】
下面程序有错吗?
public class Something {
void doSomething() {
private String s = "";
int l = s.length();
}
}
答案: 错。局部变量前不能放置任何访问修饰符(private,public和protected)。final可以用来修饰局部变量(final是非访问修饰符)。
l 下面程序有错吗?
public class Something {
public int addOne(final int x) {
return ++x;
}
}
答案: 错。int x被修饰成final,意味着x是常量,不能在addOnemethod中被修改。
下面程序有错吗?
class Something {
int i;
public void doSomething() {
System.out.println("i = " + i);
}
}
答案: 正确。输出的是"i= 0"。int i属于instant variable (实例变量,或叫成员变量)。instant variable有default value。int的default value是0。
下面程序有错吗? 和上题只有一个地方不同,就是多了一个final。这难道就错了吗?
class Something {
final int i;
public void doSomething() {
System.out.println("i = " + i);
}
}
答案: 错。final int i是个final的instant variable。final的instant variable没有default value,必须在constructor (构造器)结束之前被赋予一个明确的值。可以修改为"final int i = 0;"。
【经典问题】final关键字到底修饰了什么?
final使得被修饰的变量“不变”,但是由于对象型变量的本质是“引用”,使得“不变”也有了两种含义:引用本身的不变和引用指向的对象不变。
引用本身的不变:
final StringBuffer a=new StringBuffer("immutable");
final StringBuffer b=new StringBuffer("not immutable");
a=b;//编译器错误
引用指向的对象不变:
final StringBuffer a=new StringBuffer("immutable");
a.append(" broken!"); //编译通过
System.out.println(a); //运行结果:immutable broken!
可见,final只对引用的“值”(即引用所指向的那个对象的内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译器错 误。至于它所指向的对象的变化,final是不负责的。这很类似==操作符:==操作符只负责引用的“值”相等,至于这个地址所指向的对象内容是否相 等,==操作符是不管的。
理解final问题有很重要的含义。许多程序漏洞都基于此——final只能保证引用永远指向固定对象,不能保证那个对象的状态不变。在多线程的操 作中,一个对象会被多个线程共享或修改,一个线程对对象无意识的修改可能会导致另一个使用此对象的线程崩溃。一个错误的解决方法就是在此对象新建的时候把 它声明为final,意图使得它“永远不变”。其实那是徒劳的。
【思考】final和abstract是否可同时修饰一个方法
内部类
class EnclosingOne{
public class InsideOne{}
}
public class InnerTest{
public static void main(String args[]){
EnclosingOne eo=new EnclosingOne();
//insert code here
}
}
A.InsideOne ei=eo.new InsideOne();
B.eo.InsideOne ei=eo.new InsideOne();
C.InsideOne ei=EnclosingOne.new InsideOne();
D.InsideOne ei=eo.new InsideOne();
E.EnclosingOne.InsideOne ei=eo.new InsideOne();
Answer:E
包装类:扩充功能
基本数据类型的变量一般分配在栈上,其包装类的对象分配在堆上。
八种:Integer、Character、Boolean、Byte、Short、Long、Double、Float
Integer:
常用方法:
Integer.parseInt(..)
Integer.valueOf(..)
Integer.toString(..)
【程序示例】
l 判断从控制台输入的是否是一个整数
import java.util.Scanner;
public class E {
public static void main(String[]args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入整数:");
try {
int i = Integer.parseInt(sc.next());
System.out.println(i);
} catch (Exception e) {
System.out.println("您输入的不是整数!");
}
}
}