类和对象
面向对象的概念
面向过程
核心是过程二字,过程指的是解决问题的步骤,设计一条流水线,机械式的思维方式;
在一个结构体中定义汽车的品牌、颜色、各项性能参数等属性,对汽车操作的函数与汽车本身的定义没有任何关系,如DriveCar,RepairCar,这些函数都需要接受一个代表要被操作的汽车参数 ,是一种谓语与宾语的关系。
面向对象
核心就是对象二字,对象就是特征与技能的结合体,利用“类”和“对象”来创建各种模型来实现对真实世界的描述。
定义汽车时,除了要指定在面向过程中规定的那些属性,如品牌、颜色、各项性能参数等外,还要指定该汽车可能具有的动作 ,如行驶等。这些函数被调用时,都是以某辆汽车要行驶的语法格式来使用的 ,这是一种主语与谓语的关系。
面向对象的三大特征
封装 (Encapsulation)
继承 (Inheritance)
多态 (Polymorphism)
面向对象的思想概述
面向对象的编程思想力图使计算机语言中对事物的描述与现实世界中该事物的本来面目尽可能的一致。
类(class)和对象(object)是面向对象方法的核心概念。类是对一类事物描述,是抽象的、概念上的定义;对象是实际存在的该类事物的每个个体,因而也称实例(instance)。
如果将对象比作汽车,那么类就是汽车的设计图纸。所以面向对象程序设计的重点是类的设计,而不是对象的设计。
类的定义
public class Animal
{
public int legs;
public void eat(){
System.out.println("Eating.");
}
public void move(){
System.out.println("Moving.");
}
}
legs是类的属性,也叫类 成员 变量。
eat,move是方法也叫类的 成员 函数。
声明类
语法格式
[< 修饰符>] class < 类名>
{
[]
[]
[]
}
说明:
修饰符public:类可以被任意访问
类的正文要用{ }括起来
举例:
public class Person{
private int age ; //声明私有变量 age
public void showAge(int i) { //声明方法showAge
age = i;
}
}
声明属性
语法格式:
[] 类型 < 属性名> [=初值] ;
说明:
修饰符 private: 该属性只能由该类的方法访问。
修饰符 public: 该属性可以被该类以外的方法访问。
类型:任何基本类型,如int、boolean或任何类。
属性有时也称为:数据成员(数据),成员变量(变量)
举例
public class Person{
private int age; //声明private变量 age
public String name = “Lila”; //声明public变量 name
}
声明方法
语法格式:
([< 参数表>]) {
[< 语句>]
}
说明:
修饰符:public,private,protected 等。
返回类型:return语句传递返回值。没有返回值:void。
// 没有返回值的方法(void修饰的方法): 也可以使用 return 表示方法结束
// return 标识方法结束, return 的后面不能再有其他语句
方法有时也称为:成员函数(函数)
举例:
public class Person{
private int age;
public int getAge() { return age; } //声明方法getAge
public void setAge(int i) { //声明方法setAge
age = i; //将参数i的值赋给类的成员变量age
}
}
对象的创建和使用
使用 new +构造方法 创建一个新的对象;
当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。(类中,jvm给成员变量赋了初始值,不会给局部变量赋初始值,局部变量没有默认值)
除了基本数据类型之外的都是变量类型都是引用类型。
byte 0
short 0
int 0
long 0
float 0.0
double 0.0
char 空字符
boolean false
String null
使用 “对象名.对象成员” 的方式访问对象成员(包括属性和方法);
public class Animal {
public int legs;
public void eat(){
System.out.println(“Eating.”);
}
public viod move(){
System.out.println(“Move.”);
}
}
public class Zoo{
public static void main(String args[]){
Animal xb=new Animal();
xb.legs=4;
System.out.println(xb.legs);
xb.eat();
xb.move();
}
}
如果创建了一个类的多个对象,对于类中定义的属性,每个对象都拥有各自的一套副本,且互不干扰。
public class Zoo{
public static void main(String args[]){
Animal xb=new Animal();
Animal xh=new Animal();
xb.legs=4;
xh.legs=0;
System.out.println(xb.legs); //4
System.out.println(xh.legs); //0
xb.legs=2;
System.out.println(xb.legs); //2
System.out.println(xh.legs); //0
}
}
类的访问机制
在一个类中的访问机制:类中的方法可以直接访问类中的成员变量。(有一个例外:static方法访问非static,编译不通过)
在不同类中的访问机制:先创建要访问类的对象,再用对象访问类中定义的成员。
对象的生命周期
有变量引用(指向)对象,对象就不会成为垃圾对象。
下图,Person对象没成为垃圾对象,还可以被继续使用
匿名对象
定义:没有变量引用,使用一次就变成垃圾,立即被销毁
我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象, 如:new Person().shout();
如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
我们经常将匿名对象作为实参传递给一个函数调用。
信息的封装和隐藏
使用者对类内部定义的属性(对象的成员变量)的直接操作会导致数据的错误、混乱或安全性问题。应该将属性保护起来,防止乱用。
通过信息隐藏来实现,Java中通过将属性声明为私有的(private),再提供公开的(public)方法:getXXX和setXXX实现对该属性的操作,以实现下述目的:
隐藏一个类的实现细节;
使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作;
便于修改,增强代码的可维护性;
public class Animal{
private int legs; //将属性legs定义为private,只能被Animal类内部访问
public void setLegs(int i){ //在这里定义方法 eat() 和 move()
if (i != 0 && i != 2 && i != 4){
System.out.println("Wrong number of legs!");
return;
}
legs=i;
}
public int getLegs(){
return legs;
}
}
public class Zoo{
public static void main(String args[]){
Animal xb=new Animal();
xb.setLegs(4); //xb.setLegs(-1000);
xb.legs=-1000; //非法
System.out.println(xb.getLegs());
}
}
构造方法
构造方法的特征
它具有与类相同的名称;
它不含返回值;
注意:在构造方法里不含返回值的概念是不同于“void”的,在定义构造方法时加了“void”,结果这个方法就不再被自动调了。
构造方法的作用
当一个类的实例对象刚产生时,这个类的构造方法就会被自动调用,我们可以在这个方法中加入要完成初始化工作的代码。这就好像我们规定每个“人”一出生就必须先洗澡,我们就可以在“人”的构造方法中加入完成“洗澡”的程序代码,于是每个“人”一出生就会自动完成“洗澡”,程序就不必再在每个人刚出生时一个一个地告诉他们要“洗澡”了。
所以,构造器的功能就是:创建类的实例时,对类的属性进行初始化。
构造器的定义
语法格式:
< 修饰符> ([< 参数表>]) {
[< 语句>]
}
举例:
public class Animal {
private int legs;
public Animal() {legs = 4; } // 构造器
public void setLegs(int i) { legs = i; }
public int getLegs(){return legs;}
}
创建Animal类的实例:Animal a=new Animal(); // 调用构造器,将legs初始化为4
注释:构造器的名称必须与类名相同。修饰符:public、private、protected,构造器不是方法,没有返回值(连void也不能写)
默认的构造方法
Java语言中,每个类都至少有一个构造方法;如果类的定义者没有显式的定义任何构造方法,系统将自动提供一个默认的构造方法:
默认构造方法没有参数
默认构造方法没有方法体
默认的构造方法:JVM 会为每一个类提供一个默认的无参数的构造器: public Animal(){}
所以,不编写构造方法就能用new Xxx()创建类的实例。
Java类中,一旦类的定义者显式定义了一个或多个构造方法,系统将不再提供默认的构造方法;
构造器的主要作用:利用构造器参数初始化对象的属性。
创建对象时,内存变化
上面第一个图,name和age是赋默认的初始值
上面第二个图,name和age是声明成员变量时赋值
上面第三个图,构造器为成员变量赋值
上面第四个图,建立引用
调用构造器前对象就创建了(上面一、二图可知)。
方法的重载
函数
定义一个函数的格式
返回值类型 函数名(参数类型 形式参数1,参数类型 形式参数2,….){
程序代码
return 返回值;
}
形式参数:在方法被调用时用于接收外部传入的数据的变量。
参数类型:就是该形式参数的数据类型。
返回值:方法在执行完毕后返还给调用它的程序的数据。
返回值类型:函数要返回的结果的数据类型。
实参:调用函数时实际传给函数形式参数的数据。
函数的重载
函数的重载就是在同一个类中允许同时存在一个以上的同名函数,只要它们的参数个数或类型不同即可。 【方法名相同,参数类型列表独一无二,其余随意,应用场景:构造器的重载】
重载方法的参数列表必须不同:参数个数、个数相同比较对应位置形参类型;不能依靠形参参数名
重载方法的返回值类型可以相同,也可以不同
调用时根据方法的参数来区别
public class PrintStream{
public void print(int i) {……}
public void print(float f) {……}
public void print(String s) {……}
}
举例一
package com.uncleyong;
public class MethodOverload {
/**
* 定义三个重载方法并调用。方法名为mOL。
* 三个方法分别接收一个int参数、
* 两个int参数、
* 一个字符串参数。
* 分别执行平方运算并输出结果,相乘并输出结果,输出字符CallMOL串信息。
*/
public void mOL(String str){
System.out.println("CallMOL:" + str);
}
public void mOL(int a, int b){
System.out.println(a * b);
}
public void mOL(int a){
System.out.println(a * a);
}
}
1 packagecom.uncleyong;2
3 public classTestMethodOverload {4
5 public static voidmain(String[] args) {6 MethodOverload mo = newMethodOverload();7
8 mo.mOL(5);9 mo.mOL("abc");10 mo.mOL(3, 4);11 }12
13 }
举例二
package com.uncleyong;
public class TestMethodOverload {
public static void main(String[] args) {
MethodOverload mo = new MethodOverload();
System.out.println(mo.max(1.1, 3.1));
System.out.println(mo.max(1, 4, 3.1));
System.out.println(mo.max(3, 5));
}
}
class MethodOverload {
/**
* 定义三个重载方法max,
* 第一个方法求两个int值中的最大值,
* 第二个方法求两个double值中的最大值,
* 第三个方法求三个double值中的最大值,并分别调用三个方法。
*/
public int max(int a, int b){
return a > b ? a : b;
}
// public double max(double a, double b){
// return a > b ? a : b;
// }
public double max(double a, double b, double c){
return this.max(this.max(a, b), c);
}
// 上面方法调用了下面,说明重载方法顺序无所谓
public double max(double a, double b){
return a > b ? a : b;
}
}
方法的重载是多态的一种表现形式(同样的对象,调同样方法名的方法,传参不一样,表现形式也不一样)
Java中方法不能重载的三种情况
1.对于两个方法,如果只有返回值不同,那么不构成重载,程序会报错。
2.对于两个方法,如果只有访问修饰符不同,那么不构成重载,程序会报错。
3.对于两个方法,如果只是参数命名不同,那么不构成重载,程序会报错。
构造方法重载
构造方法一般用来创建对象的同时初始化对象,构造方法重载使得对象的创建更加灵活,方便创建各种不同的对象
构造方法重载(有多个构造方法,比如,无参,有参)和函数重载一样,参数列表必须不同
构造方法重载举例:
public class Person{
public Person(String name, int age, Date d) {this(name,age);}
public Person(String name, int age) {…}
public Person(String name, Date d) {…}
public Person(){…}
}
构造方法重载,参数列表必须不同
idea生成构造方法
选择第一个
可以选择对应的属性,生成构造函数,也可以都不选,生成无参的构造函数
重载方法的顺序
先后顺序无关
Hello.java
package com.test;
public class Hello{
String str;
int i;
public Hello(String str, int i){
this(str); // 调用一个String类型形参的构造函数;如果写成Hello(str);,编译报错,这样写变成了方法调用,但是没有Hello方法;其实调用构造器前,对象已经创建,so,用this
i = i;
}
public Hello(String str){
this.str = str;
}
}
Test.java
package com.test;
public class Test {
public static void main(String[] args) {
Hello hello = new Hello("qzcsbj", 1);
System.out.println(hello.str);
System.out.println(hello.i);
}
}
this 关键字
this是什么
每个成员方法内部,都有一个this引用变量,指向调用这个方法的对象;
成员变量前面才加this,局部变量不加this;
this.name = name;
为了区分形参name和成员变量name,成员变量前面加this
this的用途
1、在一般的方法中可以通过 this 来引用当前对象的成员(方法、属性)
2、通过 this() 调用重载的构造器. 需要注意的是, 通过此种方法调用重载的构造器的代码必须放在当前构造器的第一行
示例
package com.uncleyong;
/**
* (1)定义Person类,有4个属性:String name; int age; String school; String major,
* (2)定义Person类的3个构造方法:
* 第一个构造方法Person(String n, int a)设置类的name和age属性;
* 第二个构造方法Person(String n, int a, String s)设置类的name,age 和school属性;
* 第三个构造方法Person(String n, int a, String s, String m)设置类的name, age ,school和major属性;
*/
public class Person {
private String name;
private int age;
private String school;
private String major;
public Person(String n, int a, String s, String m){
this(n, a, s);
major = m;
}
public Person(String n, int a, String s){
this(n, a);
school = s;
}
public Person(String n, int a){
name = n;
age = a;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getSchool() {
return school;
}
public String getMajor() {
return major;
}
public void setName(String name) {
this.name = name;
}
}
注意:上面this(n, a);不能写成Person(n, a);
因为调用构造器前,对象已经创建了,需要给当前这个对象初始化,就需要用this,且Person(n,a)是调用Person方法,没有这个方法,哪怕有,方法首字母也不会大写
package com.uncleyong;
public class TestPerson {
public static void main(String[] args) {
Person person = new Person("uncleyong", 15, "北大", "Java");
System.out.println(person.getName());
System.out.println(person.getAge());
System.out.println(person.getSchool());
System.out.println(person.getMajor());
}
}
传值、传引用
传基本数据类型
class Test{
public static void main(String [] args){
int x = 5;
change(x);
System.out.println(x);
}
public static void change(int y){
y = 3;
}
}
第四步,上面change执行完后,里面的局部变量当垃圾回收了
传引用
示例一
public class Test{
int x;
public static void main(String [] args){
Test obj = new Test();
obj.x = 5;
change(obj);
System.out.println(obj.x);
}
public static void change(PassRef obj){
obj.x = 3;
}
}
示例二
public class Test{
int x;
public static void main(String [] args){
Test obj = new Test();
obj.x = 5;
change(obj);
System.out.println(obj.x);
}
public static void change(Test obj){
obj = new Test(); // 更新了obj的值
obj.x = 3;
}
}
实例:
将对象作为参数传递给方法。
题目要求:
(1)定义一个Circle类,包含一个double型的radius属性代表圆的半径,一个findArea()方法返回圆的面积。
(2)定义一个类PassObject,在类中定义一个方法printAreas(),该方法的定义如下:
public void printAreas(Cirlce c, int time)
在printAreas方法中打印输出1到time之间的每个整数半径值,以及对应的面积。例如,times为5,则输出半径1,2,3,4,5,以及对应的圆面积。
在main方法中调用printAreas()方法,调用完毕后输出当前半径值。程序运行结果如图所示。
package com.uncleyong;
/**
* 定义一个类PassObject,在类中定义一个方法printAreas(),该方法的定义如下:
* public void printAreas(Cirlce c, int time)
*
*/
public class PassObject {
//在printAreas方法中打印输出1到time之间的每个整数半径值,以及对应的面积。
//例如,times为5,则输出半径1,2,3,4,5,以及对应的圆面积。
public void printAreas(Circle c, int time){
System.out.println("radius\t\tarea");
for(int i = 1; i <= time; i++){
c.setRadius(i);
System.out.println(c.getRadius() + "\t\t" + c.findArea());
}
}
public static void main(String[] args) {
PassObject passObject = new PassObject();
Circle circle = new Circle();
passObject.printAreas(circle, 5);
}
}
/**
* (1)定义一个Circle类,包含一个double型的radius属性代表圆的半径,一个findArea()方法返回圆的面积。
*/
class Circle {
private double radius;
public void setRadius(double radius) {
this.radius = radius;
}
public double getRadius() {
return radius;
}
//返回圆的面积
public double findArea(){
return radius * radius * 3.14;
}
}
package语句、import语句
软件包帮助管理大型软件系统:将语义近似的类组织到包中。
包可以包含类和子包。
package语句
package语句作为Java源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。
它的格式为:
package [.]* ;
示例
package p1; //指定类Test属于包p1
public class Test{
public void display(){
System.out.println("in method display()");
}
}
包对应于文件系统的目录,package语句中用‘ .’ 来指明包(目录)的层次;
包通常用小写单词。
编译和生成包
如果在程序Test.java中已定义了包p1,编译时采用如下方式:
-d将生成的类文件的路径更改为另一个目录。
javac -d destpath Test.java,则编译器会自动在destpath目录下建立一个子目录p1,并将生成的.class文件都放到destpath/p1下。
javac Test.java,则编译器会在当前目录下生成Test.class文件,再在适合位置(destpath目录)手动创建一个名为p1的子目录,将Test.class复制到该p1目录下。
import语句
为使用定义在不同包中的Java类,需用import语句来引入所需要的类。
Import语句告诉编译器到哪里去寻找类。
语法格式:
import 包名[.子包名…].
应用举例:
import p1.Test; // import p1.*; 表示引入p1包中的所有类
public class TestPackage{
public static void main(String args[]){
Test t = new Test(); //Test类在p1包中定义
t.display();
}
}
JDK中主要的包介绍
java.lang----包含一些Java语言的核心类,如String、Math、Integer、System和Thread,提供常用功能。
java.awt----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
java.applet----包含applet运行所需的一些类。
java.net----包含执行与网络相关的操作的类。
java.io----包含能提供多种输入/输出功能的类。
java.util----包含一些实用工具类,如定义系统特性、使用与日期日历相关的函数。
练习题(参考答案已放在Q群文件中)
1、分别用面向过程和面向对象的思维,计算圆的面积;
TestCircle.java
2、下面代码结果是?要实现写法二的功能,写法一如何优化?
写法一:
Person.java
public class Person {
private int age;
public void setAge(int age){
if(age<0 || age>130){
System.out.println("年龄范围只能是0-130,您输入的是:" + age);
return;
}
age = age;
}
public int getAge(){
return age;
}
}
TestPerson.java
class TestPerson {
public static void main(String[] args) {
Person p = new Person();
p.setAge(101);
System.out.println("age = " + p.getAge());
Person p2 = new Person();
System.out.println("age = " + p2.getAge());
}
}
age = 0
age = 0
说明,局部变量和类属性同名,setAge方法里,age=age,两个age都是局部变量,而不是成员变量
写法二:
Person.java
public class Person {
private int age;
public void setAge(int age2){
if(age2<0 || age2>130){
System.out.println("年龄范围只能是0-130,您输入的是:" + age2);
return;
}
age = age2;
}
public int getAge(){
return age;
}
}
TestPerson.java
class TestPerson {
public static void main(String[] args) {
Person p = new Person();
p.setAge(101);
System.out.println("age = " + p.getAge());
Person p2 = new Person();
System.out.println("age = " + p2.getAge());
}
}
age = 101
age = 0
写法一实现写法二的优化方式
改为:this.age = age; // 说明this.age是成员变量
3、定义Boy、Girl类,对象boy给对象girl求婚,且girl答复;
TestThis.java