文章目录
面向对象编程三大特性:
- 封装性
- 继承
- 多态(重载和重写)
基本内容
类包括:成员变量和方法,方法体内的局部变量只在方法内有效
成员变量有默认值,但局部变量没有默认值,可能无法通过编译。
类是一种数据类型,用类生命的变量称为对象。
构造方法无类型且与类名名字相同,如果类中无构造方法,new的时候系统调用默认的构造方法;如果类中定义了>1个构造方法,则无参的构造方法可能无效。
Apple apple = new Apple(), 其中new Apple()是一个值,把该引用赋给apple。
对于基本数据类型,向参数(即局部变量)传值,传的是复印件,不影响原件。
对于引用数据类型,传参是传引用。
Variable.java
package com.Clazz;
//成员变量与局部变量
class A{
int m = 10,sum = 0;
void f(){
if(m>9){
int z =10;
z = 2*m+z;
}
for(int i=0;i<m;i++){
sum+=i;
}
//z = i + sum; z和i已无效
System.out.println("m="+m);
System.out.println("sum="+sum);
}
}
//this关键字使用成员变量的值
class Thiz{
int x = 10,y;
void f(){
int x = 5;
y = x+x;
System.out.println("-----this关键字------");
System.out.println("y="+y);
y = x + this.x;
System.out.println("y="+y);
}
}
//对于基本数据类型,传参只是传复印件
class Calculate{
int add(int x,int y){
x = 2*x;
y = 3*y;
return x+y;
}
}
//传参引用
class Apple{
int appleNum = 10;
}
class People{
void eatApple(Apple apple){ //此处要加上类名
apple.appleNum-=2;
}
}
//可变参数
class Sum{
public int getSum(int...x){ //不确定个数的int型参数
int sum = 0;
for(int i=0;i<x.length;i++){
sum+=x[i];
}
return sum;
}
}
public class Variable {
public static void main(String[] args) {
A a = new A();
a.f();
Thiz thiz = new Thiz();
thiz.f();
Thiz th = new Thiz();
th = thiz; //垃圾回收机制,两对象具有相同的引用=二者具有相同的实体(成员变量)——>释放不被任何对象拥有的实体
Calculate calculate = new Calculate();
int m = 100;
int n = 200;
int result = calculate.add(m,n);
System.out.println("-----复印件------");
System.out.println("result="+result);
result = calculate.add(300+m,2*n);
System.out.println("result="+result);
System.out.println("-----传参引用------");
Apple apple = new Apple();
People people = new People();
System.out.println("apple原有数量="+apple.appleNum+"个");
people.eatApple(apple);
System.out.println("被人类吃后还剩"+apple.appleNum+"个");
System.out.println("-----可变参数------");
Sum s = new Sum();
System.out.println("sum="+s.getSum(12,23,34,45));
}
}
运行结果:
com.Clazz.Variable
m=10
sum=45
-----this关键字------
y=10
y=15
-----复印件------
result=800
result=2000
-----传参引用------
apple原有数量=10个
被人类吃后还剩8个
-----可变参数------
sum=114
Process finished with exit code 0
对象组合 VS 类继承
《极限编程》(Extreme programming)的指导原则之一是“只要能用,就做最简单的”。一个似乎需要继承的设计常常能够戏剧性地使用组合来代替而大简化,从而使其更加灵活。因此,在考虑一个设计时,问问自己:“使用组合是不是更简单?这里真的需要继承吗?它能带来什么好处?”
继承和组合的比较:
面向对象系统中功能复用的两种最常用技术是类继承和对象组合(object composition)。正如我们已解释过的,类继承允许你根据其他类的实现来定义一个类的实现。这种通过生成子类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,父类的内部细节对子类可见。
对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。
继承和组合各有优缺点。类继承是在编译时刻静态定义的,且可直接使用,因为程序设计语言直接支持类继承。类继承可以较方便地改变被复用的实现。当一个子类重定义一些而不是全部操作时,它也能影响它所继承的操作,只要在这些操作中调用了被重定义的操作。
但是类继承也有一些不足之处。首先,因为继承在编译时刻就定义了,所以无法在运行时刻改变从父类继承的实现。更糟的是,父类通常至少定义了部分子类的具体表示。因为继承对子类揭示了其父类的实现细节,所以继承常被认为“破坏了封装性” 。**子类中的实现与它的父类有如此紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。当你需要复用子类时,实现上的依赖性就会产生一些问题。**如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。一个可用的解决方法就是只继承抽象类,因为抽象类通常提供较少的实现。
对象组合是通过获得对其他对象的引用而在运行时刻动态定义的。组合要求对象遵守彼此的接口约定,进而要求更仔细地定义接口,而这些接口并不妨碍你将一个对象和其他对象一起使用。这还会产生良好的结果:因为对象只能通过接口访问,所以我们并不破坏封装性;只要类型一致,运行时刻还可以用一个对象来替代另一个对象;更进一步,因为对象的实现是基于接口写的,所以实现上存在较少的依赖关系。
对象组合对系统设计还有另一个作用,即优先使用对象组合有助于你保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物。另一方面,基于对象组合的设计会有更多的对象 (而有较少的类),且系统的行为将依赖于对象间的关系而不是被定义在某个类中。
这导出了我们的面向对象设计的第二个原则:优先使用对象组合,而不是类继承。
组合有什么优点(为什么用组合)
1)简化代码
2)便于代码统一维护
Volme.java
package com.Clazz;
class Circle{
double radius,area;
void setRadius(double r){
radius = r;
}
double getRadius(){
return radius;
}
double getArea(){
area = 3.14*radius*radius;
return area;
}
}
class Circular{
Circle bottom;
double height;
void setBottom(Circle c){ //设置圆锥的底是一个Circle对象
bottom = c;
}
void setHeight(double h){
height = h;
}
double getVolme(){
if(bottom == null)
return -1;
else
return bottom.getArea()*height/3.0;
}
double getBottomRadius(){
return bottom.getRadius();
}
public void setBottomRadius(double r){
bottom.setRadius(r);
}
}
public class Volme {
public static void main(String[] args) {
//一个半径为10的圆
Circle circle = new Circle();
circle.setRadius(10);
Circular circular = new Circular();
System.out.println("circle 的引用:" + circle);
System.out.println("圆锥的bottom的引用:" + circular.bottom);
//在没有执行之前,null
circular.setHeight(5);
circular.setBottom(circle);
System.out.println("circle 的引用:" + circle);
System.out.println("圆锥的bottom的引用:" + circular.bottom);
System.out.println("圆锥的体积:" + circular.getVolme());
System.out.println("修改circle的半径,bottom的半径同样变化");
circle.setRadius(20);
System.out.println("bottom的半径:" + circular.getBottomRadius());
System.out.println("重新创建circle,circle的引用将发生变化");
circle = new Circle();
System.out.println("circle 的引用:" + circle);
System.out.println("但不影响circular的bottom的引用");
System.out.println("圆锥的bottom的引用:" + circular.bottom);
System.out.println("圆锥的体积:" + circular.getVolme());
}
}
运行结果:
circle 的引用:com.Clazz.Circle@6d311334
圆锥的bottom的引用:null
circle 的引用:com.Clazz.Circle@6d311334
圆锥的bottom的引用:com.Clazz.Circle@6d311334
圆锥的体积:523.3333333333334
修改circle的半径,bottom的半径同样变化
bottom的半径:20.0
重新创建circle,circle的引用将发生变化
circle 的引用:com.Clazz.Circle@5fd0d5ae
但不影响circular的bottom的引用
圆锥的bottom的引用:com.Clazz.Circle@6d311334
圆锥的体积:2093.3333333333335
Process finished with exit code 0
解析:
核心语句在于
class Circular{
Circle bottom;
double height;
void setBottom(Circle c){ //设置圆锥的底是一个Circle对象
bottom = c;
} }
对象没有setBottom时,因为对象circular只是声明变量bottom,没有new分配内存,所以引用为null。当赋值后,有了引用,且指向相同。
实例变量与类变量
- 成员变量可以细分为实例变量和类变量(static)
- 不同对象的实例变量占据不同的内存空间,但是所有对象共享类变量。
- 类名.类变量 , 就可访问类变量,也可对象.类变量访问
MemberVariable.java
package com.Clazz;
class Lader{
double up,height;
static double down;
public static double getDown() {
return down;
}
public static void setDown(double down) {
Lader.down = down;
}
public double getUp() {
return up;
}
public void setUp(double up) {
this.up = up;
}
}
public class MemberVariable {
public static void main(String[] args) {
Lader.down = 100;
Lader laderOne = new Lader();
Lader laderTwo = new Lader();
laderOne.setUp(20);
laderTwo.setUp(30);
System.out.println("laderOne的上底:"+laderOne.getUp());
System.out.println("laderTwo的上底:"+laderTwo.getUp());
System.out.println("下底:"+Lader.down); //不可以直接输出down
System.out.println("laderOne的下底:"+laderOne.getDown());
System.out.println("laderTwo的下底:"+laderTwo.getDown());
}
}
运行结果:
laderOne的上底:20.0
laderTwo的上底:30.0
下底:100.0
laderOne的下底:100.0
laderTwo的下底:100.0
Process finished with exit code 0
实例方法和类方法
- 只有当类创建对象时,实例方法才分配入口地址,且被所有对象共享。
- 如果一个方法不需要操作类中的任何实例变量,则考虑设计为static方法,例如java类库提供的Arrays类中的许多方法都是static方法。
BinarySearch.java
package com.Clazz;
import java.util.*;
//二分查找
public class BinarySearch {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int []a = {12,23,9,4,23,88,76,40,50,10};
Arrays.sort(a);
System.out.println(Arrays.toString(a));
System.out.println("输入数据,程序判断该整数是否在数组中:");
int number = scanner.nextInt();
int index = Arrays.binarySearch(a,number);
if(index>=0)
System.out.println(number+"和数组中索引为"+index+"的元素值相同");
else
System.out.println(number+"不与数组中的任一元素相同");
}
}
[4, 9, 10, 12, 23, 23, 40, 50, 76, 88]
输入数据,程序判断该整数是否在数组中:
23
23和数组中索引为4的元素值相同
Process finished with exit code 0
this关键字
- 代表当前对象
- 初始化时,通常可省略实例成员变量名字前的this和static变量前的“类名.”
- 当实例成员变量与局部变量名字相同时,由于优先局部变量,所以须用this.或类名.访问成员变量。
- this不能出现在类方法中,因为该类可能还没有任何对象诞生。
import关键字
- 若要调用的类不在同一包内,须用import
- 若import整个包中的类,则会增加编译时间,但不会影响程序运行性能,因为当程序执行时,只有真正使用的字节码文件加载到内存。
方法重载
- 一个类中可以有多个具有相同名字的方法,但是方法的参数(个数|类型)不同
访问权限
- 友好变量、友好方法:不用private、public及protected修饰,其有效范围为同一包内。
- protected变量和方法,其有效范围也是须在同一个包中。
Authority.java
package com.Clazz;
class Student{
private int sno;
public void setSno(int sno) {
this.sno = sno;
}
public int getSno(){
return sno;
}
}
public class Authority {
public static void main(String[] args) {
Student stu = new Student();
// stu.sno = 20; 非法,private变量须用set方法赋值
stu.setSno(123);
// System.out.println("sno="+stu.sno); 非法,private变量须用get方法获取
System.out.println("sno="+stu.getSno());
}
}
运行结果:
我的学号是:9901 我的姓名是:Tom
婚否=false
Process finished with exit code 0
ConvertChar.java
package com.Clazz;
//基本类型的封装类
public class ConvertChar {
public static void main(String[] args) {
char a[] = {'a','c','E','A','b'};
for(int i=0;i<a.length;i++){
if(Character.isLowerCase(a[i]))
a[i] = Character.toUpperCase(a[i]);
else if(Character.isUpperCase(a[i]))
a[i] = Character.toLowerCase(a[i]);
}
for(int i=0;i<a.length;i++){
System.out.println(" "+ a[i]);
}
}
}
运行结果:
A
C
e
a
B
Process finished with exit code 0
其他
-
对象数组 stu = new Student[10];
-
import关键字
- 若要调用的类不在同一包内,须用import
- 若import整个包中的类,则会增加编译时间,但不会影响程序运行性能,因为当程序执行时,只有真正使用的字节码文件加载到内存。
-
javadoc
-
JRE扩展