面向对象程序设计:OOP
定义对象和类:
对象:代表的是现实世界中可以明确标识的一个实体,比如一个学生,一个老师。每一个对象都有自己独特的标识、状态和行为
1, 对象的状态(特征、属性),指的是一个数据域,比如一个学生有名字,有性别,有住址
2, 对象的行为(动作),它是由方法来定义的,调用对象的行为就是要求对象完成一个动作,比如老师上课这是一个行为
一般是使用一个通用的类来定义同一类型的对象,类是一个模板,是一个蓝图是一个合约,它用来定义对象的数据域是什么及可以做什么动作。一个对象是类的一个实例,可以从一个类中创建多个实例,这个创建的过程就称为实例化。
比如有一个Circle类:
Class Name:Circle
Data Fields:
radius
Methods:
getArea
通过上面的类或以实例化出各种具体半径大小的圆
对于数据域Java使用变量定义,使用方法来定义动作。除这些外类还有一种特殊的方法称作为构造方法,我们调用构造方法可以创建出一个新的对象,对于构造方法它本身是可以完成任何动作的但就其初衷来说还是为了完成初始化动作,初始化对象的数据域
比如:
class Circle{
double radius = 1.0;à数据域
Circle(){
} à构造方法
Circle( double newRadius){
radius = newRadius;
} à构造方法
double getArea(){
return radius*radius*Math.PI;
} à普通行为
}
可以看到对于上面的Circle类当中是没有main方法的,因而是不能运行的它只是对圆对象的定义而已。
UML类图:
类的模板对象和图解可以使用UML图形符号进行标准化。在类图中的表示如下
数据域: dataFieldName:dataFieldType
构造方法: ClassName(parameterName:parameterType)
普通方法: methodName(parameterName:parameterType): returnType
具体的对象的表示就只有数据域
类的定义与创建:
public class TestCircle1 {
public static void main(String[] args) {
// 创建一个默认的Circle1对象
Circle1 circle1 =
new Circle1();
System.
out.println("circle1的半径是:" + circle1.radius + ",面积是:"
+ circle1.getArea());
// 创建一个指定半径的Circle1对象
Circle1 circle2 =
new Circle1(25);
System.
out.println("circle2的半径是:" + circle2.radius + ",面积是:"
+ circle2.getArea());
// 修改circle2这个对象的属性,也就是把它的数据域中的数据进行更改
circle2.radius = 100;
System.
out.println("修改后circle2的半径是:" + circle2.radius + ",面积是:"
+ circle2.getArea());
}
}
class Circle1 {
// 数据域
double radius;
// 构造方法
Circle1() {
radius = 1.0;
}
Circle1(
double newRadius) {
radius = newRadius;
}
// 普通方法
double getArea() {
return radius * radius * Math.
PI;
}
}
上面的程序当中有两个类一个主类(包含main方法的类),这个类的主要目的就是为了测试Circle1这个类,这样的类也称为Circle1这个类的客户
注意:Java当中可以把两个类放在同一个文件当中但只能有一个是public的,并具有公共类的话则文件名与公共类名要相同。
在主类中的main方法中我们创建三个对象,而且创建的过程就是使用new关键字来调用构造方法来实现,创建的类的对象使用一个类型为类名的引用变量来引用,对于同一个类的不同对象来说它们都有自己独立的空间来存储自己的数据域中的内容而有相同的类的方法,使用类对数据域中的数据进行引用是通过类的引用变量.数据域名来实现的,对于方法的调用也是使用对象的引用变量.方法()来实现的
接下来看一个关于TV电视机的类
如上是UML画出的类图,下面用Java程序实现
public class TV {
int channel = 1;//默认的值是1
int volumeLevel = 1;//默认值为1
boolean on =
false;//默认是关闭的
//构造方法
public TV(){
}
//打开或关闭方法
public void turnOn(){
on =
true;
}
public void turnOff(){
on =
false;
}
//设置频道方法
public void setChannel(
int newChannel){
if(on&&newChannel>=1 && newChannel<=120)
channel = newChannel;
}
//设置音量
public void setVolume(
int newVolumeLevel){
if(on && newVolumeLevel>=1 && newVolumeLevel<=7)
volumeLevel = newVolumeLevel;
}
//向上调一个频道
public void channelUp(){
if(on&&channel<120)
channel++;
}
//向下调一个频道
public void channelDown(){
if(on&&channel>1)
channel--;
}
//加大一个等级声音
public void volumeUp(){
if(on&&volumeLevel<7)
volumeLevel++;
}
//减下一个等级声音
public void volumeDown(){
if(on&&volumeLevel>1)
volumeLevel--;
}
}
public class TestTV {
public static void main(String[] args) {
TV tv1 =
new TV();
tv1.turnOn();
tv1.setChannel(30);
tv1.setVolume(5);
TV tv2 =
new TV();
tv2.turnOn();
tv2.channelUp();
tv2.channelUp();
tv2.volumeUp();
// 显示最后的结果
System.
out.println("TV1的频道为:" + tv1.channel + ",音量为:" + tv1.volumeLevel);
System.
out.println("TV2的频道为:" + tv2.channel + ",音量为:" + tv2.volumeLevel);
}
}
构造方法构造对象:
构造方法的三种特性:
1, 具有与类相同的名字
2, 没有返回值类型,就算是void也是不行的
3, 构造方法是在创建一个类的时候使用new操作符时调用的,构造方法的作用是用来初始化对象
构造方法也是可以重载的,只要是方法的签名不同。
一个常见的错误是把返回类型加到构造方法的前面比如
public void TV(){}
上面的这个就不能算是构造方法了
调用构造方法是为了创建对象的要使用new关键 字
new ClassName(arguments);
通常来说一个类为提供一个没有参数的构造方法这个方法叫做无参构造方法
一个类也可以不显示的定义构造方法这个时候系统就会隐式的为你的类中插入一个无参的构造方法这是一个默认的构造方法,只有要类中没有定义任何构造方法的时候系统才会提供这样一个默认的构造方法
新创建的对象分配的内存空间是在堆中的,可以通过引用变量来访问它们
对象是通过对象引用变量来访问的,这个变量包含了对对象的引用
ClassName objectRefVar;
定义一个类也就是程序员定义了一个新的类型,可以使用这个新的类型来创建引用变量
比如:TV tv1 = new TV();
注意:看上去是对象引用变量存放了一个对象,但是实际上它只是存放了对这个对象的一个引用而已
访问对象的数据和方法:
在创建了一个对象之后,它的数据和方法可以使用圆点运算符来访问和调用,这个运算符也称作为对象成员访问运算符
objectRefVar.dataField
objectRefVar.method(args)
类中的数据域称为实例变量,它是依赖于某个具体的实例的,而行为则为实例方法因为它只能在具体的实例上调用
对于使用类来调用的方法比如:
Math.random();这样的方法在类中是由于使用
static来修饰的静态方法,而实例方法是非静态的它是与具体的实例进行绑定的。
通常来看我们创建一个对象后会把这个对象的引用赋给一个变量而也有时只是临时使用这对象这个时候就可以不用赋给一个引用变量
比如:System.out.println(“圆的面积:”+new Circle(5).getArea());
这个时候的对象称为匿名对象
引用数据域和null值
对于类中的数据域可以是任何的类型这样就有可能是一个引用类型,比如说可以是一个String类型,对于引用类型来说的话它的默认值就是null
这就比如基本类型也有一些默认值比如boolean类型的默认值是false,int类型的默认值是0,char类型的默认值是’\u0000’
但是注意对于方法中的局部变量是不会赋以默认值的因而如果是方法中的局部变量没有赋值而去使用的话编译是通不过的
。
基本类型变量和引用类型变量的区别:
每一个变量都代表了一个存储值的位置。声明一个变量时变是在告诉编译器,这一个变量可以存储什么类型的值,对于基本类型来说的话,对应内存所存储的值是基本类型值,对于引用变量来说的话对应内存所存储的值是一个引用,是对象的存储地址
如果把一个变量赋值给另一个变量的话则另一个变量就存储了与赋值的变量相同的值,对于基本类型来说就是把一个变量的实际的值赋给了另一个变量,而对于一个引用类型来说的话则是把一个变量的引用赋给了另一个变量,这个时候这两个变量就指向了同一个对象,那么那个另一个对象之前所指向的对象就不再有用,这个时候就会变为一个垃圾等待GC对它进行回收,如果认为不再需要某个对象的话可以把这个对象的引用显示的赋以null值,当对象没有被任对象引用的时候就会被GC回收
Java库中的类:
Date类:
java.util.Date类提供了与系统无关的对日期和时间的封装
构造方法:
Date():以当前时间构造一个Date对象
Date(long time);以给定的一个从GMT时间开始计算的毫秒数来得到一个Date对象
toString();返回日期时间的字符串
getTime();返回从GMT时间计算起的毫秒值
setTime(long time);在对象中设置新的从GMT时间开始计算的流逝时间毫秒值
Random类:
Math.random()可以随机获得一个0.0到1.0不包括1.0的double值,还有一种产生随机数的方法是使用java.util.Random类,它可以产生一个int,long,double,float,boolean型值
构造方法:
Random():以当前时间作为种子构造Random对象
Ranodm(long seed);以特定的种子来构造Random对象
nextInt();返回一个随机整型值
nextInt(int n);返回一个0~n不包括n之间的一个随机整数
nextLong();返回一个随机的long值
nextDouble()返回一个0.0~1.0之间的随机double值,不包括0.0和1.0
nextFolat()返回一个0.0F~1.0F之间的一个float值,不包括0.0F和1.0F
nextBoolean()返回一个随机布尔值
创建一个Random对象必须要指定一个种子,默认是以当时间作为种子,对于使用相同的种子来产生的Random对象来说它们产生的随机数也会是一样的
显示GUI组件:
在开发图形界面时会用到像JFrame(框架),JButton(按钮),JRadioButton(单选按钮),JComnboBox(组合框),JList(列表)等这些对象
import javax.swing.JFrame;
public class TestFrame {
public static void main(String[] args) {
JFrame frame1 =
new JFrame();
frame1.setTitle("Window 1");
frame1.setSize(200, 150);
frame1.setLocation(200, 100);
frame1.setDefaultCloseOperation(JFrame.
EXIT_ON_CLOSE);
frame1.setVisible(
true);
JFrame frame2 =
new JFrame();
frame2.setTitle("Window 2");
frame2.setSize(200, 150);
frame2.setLocation(410, 100);
frame2.setDefaultCloseOperation(JFrame.
EXIT_ON_CLOSE);
frame2.setVisible(
true);
}
}
可以给窗口增加按钮、标签、文本域、复选框和组合框这样的组件,这些组件都是使用类来定义的
import javax.swing.*;
public class GUIComponents {
public static void main(String[] args) {
// 创建button
JButton jbtOK =
new JButton("OK");
JButton jbtCancel =
new JButton("Cancel");
// 创建一个文本标签
JLabel jlblName =
new JLabel("输入你的名字:");
// 创建 一个文本输入框
JTextField jtfName =
new JTextField("在这里输入你的名字!");
// 创建 复选框
JCheckBox jchkBold =
new JCheckBox("Bold");
JCheckBox jchkItalic =
new JCheckBox("Italic");
// 创建 单选按钮
JRadioButton jrbRed =
new JRadioButton("Red");
JRadioButton jrbYellow =
new JRadioButton("Yellow");
// 创建一个组合框
JComboBox jcboColor =
new JComboBox(
new String[] { "Freshman",
"Sophomore", "Junior", "Senior" });
// 创建 一个面板
JPanel panel =
new JPanel();
// 把组件加到这个面板当中
panel.add(jbtOK);
panel.add(jbtCancel);
panel.add(jlblName);
panel.add(jtfName);
panel.add(jchkBold);
panel.add(jchkItalic);
panel.add(jrbRed);
panel.add(jrbYellow);
panel.add(jcboColor);
// 创建一个框架
JFrame frame =
new JFrame();
// 把面板加到这个框架当中S
frame.add(panel);
frame.setTitle("显示GUI组件");
frame.setSize(450, 100);
frame.setLocation(200, 100);
frame.setDefaultCloseOperation(JFrame.
EXIT_ON_CLOSE);
// 显示这个框架
frame.setVisible(
true);
}
}
我们看一下其实GUI中的各个组件都是Java类库中定义好的类我们只要去创建它们的对象就可以了
import javax.swing.*;
这个表示把javax.swing下的所有类都导入到这个源文件当中,当然这不是建议的写法但对于要导入多个类的时候而这些类都在一个包下的时候可以这样做,这样做的话方便但它会占用一些多余的内存,因为在加载这个源文件的时候对于在javax.swing下没有使用到的类也一起加载进来了!
关于实例变量它是与一个相应的对象进行绑定的,它不会被类中的多个对象共享,而且创建的各个对象对应的实例变量也是各不相关的。如果想要让类中的各个对象共享某个数据则要创建一个静态变量,这个也称为类变量,这个变量这时会存放到这个类公共的一个内存地址当中,这样的话当这个类的某一个对象对这个变量进行了修改的话则这个类的其它对象也会受到影响。
Java中可以创建静态的变量和静态方法,关于静态方法则是无须创建一个类的对象依然可以对其进行调用直接使用类进行调用即可。
要声明一个静态变量或是定义一个静态方法则要要这个变量或方法声明中加上static
一般来说的话类中的常量是被类中的所有对象来共享的,而且常量要声明为final
final static double PI = 3.14;
比如下面我们就创建一个类包含静态变量和静态方法:
public class Circle2 {
// 定义一个实例变量
double radius;
// 定义一个静态变量
static int
numberOfObject = 0;
// 构造方法
Circle2() {
radius = 1.0;
numberOfObject++;
}
Circle2(
double newRadius) {
radius = newRadius;
numberOfObject++;
}
// 定义一个静态方法返回类创建的对象个数
static int getNumberOfObjects() {
return
numberOfObject;
}
// 定义一个普通方法
double getArea() {
return radius * radius * Math.
PI;
}
}
public class TestCircle2 {
public static void main(String[] args) {
System.
out.println("在没有创建任何对象之前:");
System.
out.println("对象的数量为:" + Circle2.
numberOfObject);
// 创建一个对象c1
Circle2 c1 =
new Circle2();
System.
out.println("在创建c1对象之后:");
System.
out.println("c1对象的radius为:" + c1.radius + ",当前类的个数为:"
+ c1.
numberOfObject);
// 创建对象c2
Circle2 c2 =
new Circle2(5);
//对c1中的初例变量进行修改
c1.radius = 9;
System.
out.println("在创建c2对象之后:");
System.
out.println("c1对象的radius为:" + c1.radius + ",当前类的个数为:"
+ c1.
numberOfObject);
System.
out.println("c2对象的radius为:" + c2.radius + ",当前类的个数为:"
+ c2.
numberOfObject);
}
}从上面的测试可以看得出对于静态变量来说它可以在不创建对象的情况下进行访问,并且对于对象的实例变来的修改不会改变其它变量的这个实例表量的值,也就是说对于实例变量来说各个对象都会有自己独立的一份
注意对于静态变量、静态方法一般使用类名来调用这样可以提高可读性,当然用对象来调用也是可以的,比如上面的写法但是不推荐,使用类名来调用可以一目了然知道这是一个静态变量/静态变量。还要注意的就是对于静态变量和静态方法是属于类的类加载后它们就存在了,因而在静态方法中是不可以使用实例变量和实例方法的因为这个时候有可能实例变量都不存在时也就是在没有创建对象时直接用类来调用静态方法而此时这个静态方法中又用到了实例变量这时这个实例变量是不存在的,当然如果明确的用一个对象去调用是可以的。
那么如何判断一个属性或方法要定义为实例还是静态:
如果一个属性或方法是依赖于某个具体的对象的则用实例的,比如Circle类中半径,它是由于具体的对象的不同而不同的因而定义为实例
而如果一个属性或方法不依赖于一个对象而是整个类共用的特性则要定义为静态的比如Circle中圆周率它就可以定义为一个静态变量
修饰符:
public:
可以在类、方法和数据域前面使用public修饰符,它表示可以被任何的其它类访问,如果没有指定任何的修饰符的话则它们可以被同一包中的任何一个类访问,这称为包内访问
对于包它是用来组织类的,可以源代码的前面加上
package packageName;
在语句之前不要存在任何的注释和空白,而且包是有层级关系的就像一个目录结构一样比如:
package p1.p2.p3;
在一个项目当中类要保持唯一性是指的包.类的唯一性类名可以相同但如果包名不同则同样不是一个类,这样的话JVM就可以唯一的找到任何一个项目中的类
如果要对于外部的类引入到源文件当中使用import 包.类;
除了public的修饰符外,Java当中还提供了private\protected修饰符
下面主要看一下private修饰符:
它限定了方法和数据只能在自己的类中进行访问
如果一个类没有定义为公共的也就是没有加上public修饰符的话则它只能在同一包内被访问
这些可见修饰符主要是用来指定数据域的方法是否能从这个类之外被访问,在一个类内部对数据和方法的访问是没有任何的限制的
比如
public class Foo{
private boolean x;
public boolean getX(){
return x; //这里是可以访问的因为在同一个类当中
}
}
public class Test{
public static void main(String[] args){
Foo foo = new Foo();
foo.getX(); //这个是可以访问的因为这个方法在Foo类中定义为public
foo.x = true; //这个是无法访问的因为这个数据在Foo类中定义为private
}
}
注意:private 中能应用在类的成员上而不能用在类上,而public 可以用在类或是类的成员上,因为一个类如果声明为private 则这个类就没有任何意义了,所以sun设计的时候就规定不可以用private 修饰一个类!
数据域的封装:
为了避免对数据域的直接修改应该使用private修饰符把数据域声明为私有的这个就是数据域的封装。
对于这种私有的数据在类的外部是不可以访问的到的但是客户常常还是需要对这些数据进行存取、修改,这个时候我们常常会提供一个相应的get方法和一个set方法来对数据域进行修改并且在这两个函数中可以对数据取出进行包装,对数据的修改进行验证。
public class Circle3 {
// 对数据域进行封装
private double radius = 1.0;
private static int
numberOfObjects = 0;
// 构造方法
public Circle3() {
// 在类的内部是可以对private修饰的属性进行读取的
numberOfObjects++;
}
public Circle3(
double newRadius) {
radius = newRadius;
numberOfObjects++;
}
// 提供get方法供外部访问封装了的数据
public double getRadius() {
return radius;
}
// 提供set方法供外部设置封装了的数据
public void setRadius(
double newRadius) {
if (newRadius >= 0)
radius = newRadius;
else
System.
out.println("提供的数据不合法!");
}
// 静态方法可以直接访问静态数据
public static int getNumberOfObjects() {
return
numberOfObjects;
}
// 定义一个普通方法来取得面积
public double getArea() {
return radius * radius * Math.
PI;
}
}
public class TestCircle3 {
public static void main(String[] args) {
Circle3 myCircle =
new Circle3(2.0);
System.
out.println("当前圆面积是:" + myCircle.getArea());
// 修改圆的半径
myCircle.setRadius(myCircle.getRadius() * 1.1);
// 修改后的圆
System.
out.println("修改半径后圆的面积是:" + myCircle.getArea());
// 得到当前的对象的个数
System.
out.println("当前总共有" + Circle3.
getNumberOfObjects() + "个圆对象!");
}
}
给方法传递对象参数
可以把一个对象传给一个方法,这样同传数组是一样的实际传的是对象的引用
public class Test {
public static void main(String[] args) {
Circle3 myCircle =
new Circle3(5.0);
//这里把一个对象作为参数传给方法,实际上是把对象的引用传给方法
printCircle(myCircle);
}
private static void printCircle(Circle3 myCircle) {
System.
out.println("圆半径是:" + myCircle.getRadius() + ",面积是:"
+ myCircle.getArea());
}
}
在Java当中只有一种传参方式也就是值传递上面的传递是把myCircle的值传给printCircle方法,这里传的值就是Circle对象的引用。
public class TestPassObject {
public static void main(String[] args) {
Circle3 myCircle =
new Circle3(1);
int n = 5;
printAreas(myCircle, n);
System.
out.println("半径现在为:" + myCircle.getRadius());
System.
out.println("n现在为:" + n);
}
private static void printAreas(Circle3 myCircle,
int n) {
System.
out.println("半径\t\t面积");
while (n >= 1) {
System.
out.println(myCircle.getRadius() + "\t\t"
+ myCircle.getArea());
myCircle.setRadius(myCircle.getRadius() + 1);
n--;
}
}
}
从上面这个例子可以看到传一个基本类型与传一个引用类型的区别所在传基要类型是把值的一个拷贝直接传过去不最后运算完后不会对原来的值有任何的影响而传引用类型过去则相当于在方法中有了传过来的引用这时方法中的引用变量与实际传过来的引用的是同一个对象在方法中对这个对象进行的修改会被保留下来。
对象数组:
对象数组指的就是一个数组是的元素全部是自定义的某一类型而不是一个基本类型。
Circle[] circleArray = new Circle[10];
这个时候为了初始化这个对象数组中的元素可以使用for循环进行一个一个初始化
for(int i = 0;i< circleArray.length;i++){
circleArray
= new Circle();
}
对象数组实际上是引用变量的数组,因而如果调用circleArray[1].getArea()则实际是调用了两层次的引用
先是从circleArrayà一个数组找到下标是1的元素à通过元素中的引用去调用getArea()方法
注意:当用new操作符创建一个数组后如果还没有初始化的话则这个数组中的每一个元素都是默认的null的引用变量
public class TotalArea {
public static void main(String[] args) {
// 声明一个Circle3类型的数组
Circle3[] circleArray;
circleArray =
createCircleArray();
printCircleArray(circleArray);
}
private static void printCircleArray(Circle3[] circleArray) {
System.
out.println("半径\t\t\t\t" + "面积");
for (
int i = 0; i < circleArray.length; i++) {
System.
out.println(circleArray
.getRadius() + "\t\t"
+ circleArray
.getArea());
}
System.
out
.println("--------------------------------------------------");
System.
out.println("面积和为:\t\t\t" +
sum(circleArray));
}
private static double sum(Circle3[] circleArray) {
double sum = 0.0;
for (
int i = 0; i < circleArray.length; i++) {
sum += circleArray
.getArea();
}
return sum;
}
private static Circle3[] createCircleArray() {
Circle3[] circleArray =
new Circle3[5];
for (
int i = 0; i < circleArray.length; i++) {
// 使用随机的半径创建各数组
circleArray
= new Circle3(Math.random() * 100);
}
return circleArray;
}
}