对象和类的学习
对对象的理解:
在现实生活中,每一个具现化的物体都可以看作一个对象,每一个抽象的方法,数据等也可以看作对象。在计算机中,比如每一个按钮,每一个图标,都可以看作一个对象。每个对象都有自己独特的标识、状态和行为,例如对于按钮,大小,形状颜色等就是它的状态,在java中被称为状态state(亦称为特征property或属性attribute)。
对类的理解:
Java中的类即为同一类型对象(拥有相同或者类似的标识、状态和行为的对象)的集合。拿生活来类比的话,静态的事物中,拥有不同大小的圆可以归为一类,不同大小的矩形可以归为一类;动态的事物中,有相同行为或者相同状态的可以归为一类,例如一些都在奔跑,一些都坐着等等。
类中有实例数据域,实例方法,构造方法和静态方法。
创建类、实例、实例数据域、构造方法、实例方法:
class SimpleRectangle{
double length = 1;
double width = 1;//两个均为实例数据域
SimpleRectangle(){
};//此处SimpleRectangle()为构造方法,用于构造一个属于该类的对象。该对象默认拥有length值为1,width值为1。
SimpleRectangle(double newlength, double newwidth){
length = newlength;
width = newwidth;//此处SimpleRectangle()同样为构造方法,用于构造一个属于该类的对象,该对象需要输入长和宽。
}
double getArea(){
return length * width;//实例方法
}
}
实例:
SimpleRectangle rectangle1 = new SimpleRectangle();
创建方法与创建数组类似。分为两步:
首先ClassName objectRefVar声明对象的引用变量(reference variable,对于引用变量的理解可参考C中的指针).
再用new ClassName(arguments)创建实例对象。
ClassName objectRefVar = new objectRefVar(arguments);
也可以直接使用new ClassName(arguments)创建一个实例对象,该对象没有相应的引用变量,被称为匿名对象。
类是一个抽象的定义,而实例就是该种抽象的具象。创建实例的过程被称为实例化。
实例数据域:
对于没有参数的构造方法建立的实例,它的数据域即为默认的数据域。在该例中即为length = 1 和 width = 1。
对于有参数的构造方法建立的实例,它的数据域即为传入数据。在该例中,若用
SimplereRtangle rectangle2 = new SimpleRectangle(2, 2);
建立一个引用变量为rectangle2的实例,则该实例的数据域为length = 2, width =2。
构造方法:
使用方法:
new ClassName(arguments);
特殊的方法:
1、构造方法的名字必须与类名相同。例如在此处类名为SimpleRectangle,则构造方法名也必须为SimpleRectangle。
2、构造方法没有返回值类型,连void也没有。
3、构造方法只有在建立一个对象实例时被调用。
构造方法的作用其实就是初始化一个对象。
与所有的其他方法一样,构造方法也可以重载。例如该例中
SimpleRectangle(){
};//无参构造法
SimpleRectangle(double newlength, double newwidth){
length = newlength;
widht = newwidth;
};//有参构造法
可以有同名的构造方法,但是签名必须不同。
一个类也可以不定义构造方法,在这种情况下类中会隐含定义一个方法体为空的无参构造方法,被称为默认构造方法(default constructor),当且仅当类中没有明确定义任何构造方法时才会自动提供它。
实例方法:
在该类中定义并且修饰符中没有静态修饰符static的即为实例方法。
实例方法依赖于实例。例如getArea()方法,依赖于某个具体的矩形(必须要有一个具体矩形的数据才能进行计算) 。
如何访问实例数据域,调用实例方法:
运用点操作符(.)通过引用变量进行访问。
double length = rectangle1.length;//将rectangle1的长度赋值给定义的变量length.
在此处引用了数据域,若在类中length和width被定义但未被赋值,对于该类数值型数据域,则默认为0;对于引用类型变量例如String,未被赋值时默认为null;对于boolean类型数据域,默认值为false;对于char类型数据域,默认值为"\u0000"。
double AreaOfRectangle1 = rectangle1.getArea();//调用实例方法getArea(),计算rectangle1的面积,并返回给变量AreaOfRectangle1。
静态变量和静态方法:
如何区别:变量名和方法名前加了静态修饰符static的即为静态变量或静态方法。
static numberOfObjects;
static int getNumberObjects(){
return numberOfObjects;
}
静态变量:不同于实例数据域,实例数据域专属于某个特殊的对象,例如初始设定的length和width专属于对象rectangle1,静态变量属于整个类中的所有对象,被类中的所有对象共享。
如何理解静态变量被类中的所有对象共享:
举例:
public class Welcome {
public static void main(String[] args) {
// TODO Auto-generated method stub
T t1 = new T();
T t2 = new T();
System.out.println("t1's i = " + t1.i + " and j = " + t1.j + " and q = " + t1.q);
System.out.println("t1's i = " + t2.i + " and j = " + t2.j + " and q = " + t2.q);
}
}
class T{
static int i = 0;
int j = 0;
int q = 0;
T(){
i++;
j = 1;
q--;
}
}
该程序最后输出为:t1's i = 2 and j = 1 and q = -1
t1's i = 2 and j = 1 and q = -1
原因:
i为static修饰符修饰的静态数据,在创建t1时i变为1,在创建t2时i变为2,因为静态变量是被类中所有对象共享的,所以最后输出时t1.i也为2,也因为i为静态变量,所以正确的访问i的方法应该为T.i,通过类访问,而不是通过对象访问;
j和q为普通实例数据,所以是实例所私有的,因此最后输出时q并不是-2而是-1。
静态方法:同理与静态变量与实例数据的关系,实例方法属于某个对象,因此实例方法必须先创建实例对象才能调用,静态方法无须创建类的实例也可调用(调用方法:ClassName.methodName。虽然也可以使用ObjectName.methodName来调用静态方法,但因为只有静态方法可以使用ClassName.methodName的方法来调用,使用该种方法调用可以提高可读性,可以一眼就看出哪个方法是静态方法,所以建议使用ClassName.methodName来调用静态方法)。
静态成员和实例成员的关系总结:
实例方法 | 实例数据域 | 静态方法 | 静态数据域 | |
实例方法 | 可调用 | 可访问 | 可调用 | 可访问 |
静态方法 | 不可调用 | 不可访问 | 可调用 | 可访问 |
用实际例子解释这份总结:
public class A{
int i = 5;
static int k = 2;
public static void main(String[] args){
int j = i;//i是实例数据,必须要使用具体对象来访问。
m1();//m1是一个实例方法,必须要使用具体对象来调用。
}
public void m1(){
i = i + k + m3(i, k);//实例方法m1调用静态方法m3。
m2();//实例方法m1调用实例方法m2,不需要依赖于某个实例。
}
public void m2(){
System.out.print("Wwy is the cuttest");
}
public static int m3(int i, int j){
return (int)(Math.pow(i, j));
}
}
正确应该为:
public class A{
int i = 5;
static int k = 2;
public static void main(String[] args){
A a = new A();//因为类A中没有明确定义的构造方法,所以此处用了默认构造方法。
int j = a.i;//i是实例数据,必须要使用具体对象来访问。
a.m1();//m1是一个实例方法,必须要使用具体对象来调用。
}
public void m1(){
i = i + k + m3(i, k);//实例方法m1调用静态方法m3。
m2();//实例方法m1调用实例方法m2,不需要依赖于某个实例。
}
public void m2(){
System.out.print("Wwy is the cuttest");
}
public static int m3(int i, int j){
return (int)(Math.pow(i, j));
}
}
关于修饰符
类、方法和数据域均有四种修饰符,分别为:public,private,protected和不加修饰符。
public修饰符代表类、方法和数据域可以被任意其他类访问;
private修饰符代表类、方法和数据域只能被自己所在的类访问;
protected修饰符之后补充;
不加修饰符代表类、方法和数据域能被同一个包内的任意一个类访问。
数据域封装
为了避免对数据的直接修改,用private修饰符将数据域声明为私有的,这称为数据域封装。
因为加了private修饰符,因此在客户端(调用该类的程序被成为该类的客户)无法直接访问和修改数据域,但有时又会存在需要访问数据域的情况,此时我们可以建立一个get方法返回数据域的值,建立一个set方法修改数据域的值。get方法被称为访问器(accessor),set方法被称为修改器(mutator)。
get方法签名:
public retrunType getPropertyName()
若返回值类型为boolean型,则习惯上用如下方法定义:
public boolean isPropertyName()
set方法签名:
public void setPropertyName(dataType propertyValue)
将对象作为参数传入方法:
1、传入的是对象的引用。
2、静态方法在接收一个引用变量时,并不是直接对该引用变量指向的对象进行操作的,而是会先制造一个副本,然后对该副本进行操作。
例:
public class Welcome {
public static void main(String[] args) {
// TODO Auto-generated method stub
Date date = null;
m1(date);
System.out.println(date);
}
public static void m1(Date date) {
date = new Date();
}
}
在该例中,按照理论:将对象传入方法时传入的是引用变量,因此方法中的修改会影响到方法外的修改。
最后输出的结果应该m1方法中建立的对象,输出程序运行时的当前时间。但事实输出的是null。
原因:在将对象date传入静态方法m1时,会自动建立一个副本,这里假定这个副本的名字为date1,此时两个引用变量date和date1均指向date对象null。方法所进行的操作date = new Date()实际是对date1进行的,因此date依然指向null,但date1却指向了新对象new Date(),所以date所指向对象并未改变,最后输出结果为null。
不可变对象和类:
由不可变类(immutable class)建立的实例就是不可变对象(immutable object)。
满足如下要求的为不可变类:
1、所有数据域都是私有的;
2、没有修改器方法;
3、没有一个返回指向可变数据域的引用的访问器方法。
例如String类就是不可变类。
变量的作用域:
目前接触的变量已经有三种:
局部变量,实例变量和静态变量。其中实例变量和静态变量统称为类变量(class variables)或数据域(date field)。
局部变量:声明和使用都在方法内部。
类变量:无论在何处声明,作用域为整个类。
当局部变量和类变量具有相同的名字时,局部变量优先,类变量被隐藏。
例:
public class F{
private int x = 0;
private int y = 0;
public F(){
}
public void p(){
int x = 1;
System.out.print("x = " + x + ", " + "y =" + y);
}
}
最后输出结果为x = 1, y = 0。
this引用:
this引用的作用:
1、引用对象自身;
2、引用隐藏数据域;
3、调用构造方法。
举三个例子对应三种作用:
例1:
public double getArea(){
return this.radius * this.radius * Math.PI;//Circle类中计算圆的面积。
}
等同于 return radius * radius * Math.PI;//因此该种情况下通常this引用省略。
例2:
class Test{
private int id;
public void m1(int id) {
this.id = id;//因为参数名id与实例变量id同名,因此实例变量id被隐藏,需要用this来引用。
}
}
例3:
class Circle{
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public Circle() {
this(1.0);//通过this调用构造方法public Cricle(double radius)。
}
}
当一个类有多个构造方法时,尽可能使用this(参数列表)的方法来实现,通常无参数或少参数的构造方法都可以用this(参数列表)调用参数多的构造方法,这样做可以简化代码,易于阅读和维护。