day3_面向对象(上)
面向对象的三条主要线索:
- Java类以及类的成员:属性、方法、构造器、代码块和内部结构
- 面向对象的三大特征:封装性,继承性,多态性
- 其它关键字:this,super,static,final,abstract,interface,package...
面向过程与面向对象
两者区别
面向对象与面向过程思想概述
思想概述Java基本元素:类和对象
类(Class)和对象(Object)是面向对象的核心概念。
- 类是对一类事物的描述,是抽象的、概念上的定义
- 对象是实际存在的该类事物的每个个体,因而也称为实例(instance)。
类与类的成员
常见的类的成员:
- 属性:对应类中的成员变量
- 行为:对应类中的成员方法
比如:
class Person {
// 属性(变量)
String name;
int age;
// 方法(函数)
public void speak(String lang) {
System.out.println("Domo, my name is " + this.name + ",I Speak " + lang + "!");
};
}
类的语法格式
语法格式对象的创建和使用
实际上,面向对象的流程:
创建类 --> 创建类的对象 --> 调用属性或方法
比如:
public class OPPtest1 {
public static void main(String[] args) {
// 创建person对象 ( 类的实例化 )
// 创建对象后,属性具有初始化值
Person p1 = new Person();
// 调用对象结构:属性、方法
// 调用格式: 对象.属性
p1.name = "Omew";
p1.age = 16;
System.out.println(p1.name);
p1.speak("Chinese");
// 创建第二个Person对象
Person p2 = new Person();
p2.name = "Kana";
p2.age = 17;
System.out.println(p2.name);
p2.speak("Jpanese");
// 将p1变量保存的对象地址赋给p3,导致p1和p3指向了堆空间中的同一个实体
Person p3 = p1;
System.out.println(p3.name); // Omew
p3.age = 10;
System.out.println(p1.age); // 10
}
内存解析
内存图
匿名对象
匿名对象比如:
/*
* 匿名对象
* 1.创建匿名对象:没有显式地赋给一个变量名,即为匿名对象
* 2.特征:一般情况下匿名对象只能调用一次
* 3.匿名对象作为参数传递给另一个对象的方法时,可以多次调用
*/
public class InstanceTest {
public static void main(String[] args) {
new Phone().price = 1999;
new Phone().showPrice(); // 0.0
//***********************************
PhoneMall mall = new PhoneMall();
mall.display(new Phone()); // 999.0
}
}
class PhoneMall{
public void display(Phone p){
p.price = 999;
p.showPrice();
}
}
class Phone{
double price;
public void showPrice(){
System.out.println("该手机的价格为:" + price);
}
}
上面程序中,每new Phone()一次就新建了一个Phone的实例,因为每个实例的首地址值都是不同的,所以直接对其进行属性赋值和方法调用,可能不会达到你想要的结果;
一般情况下,匿名对象作为参数放到方法里,才是最好的使用选择
属性
语法格式 成员变量(属性)与局部变量比如:
/*
* 类中的属性
*
* 属性(成员变量) vs 局部变脸(声明在方法内,方法形参,代码块,构造器内部的变量)
* 1.声明属性时,可以用权限修饰符进行修饰(private,public,protected,缺省)
* .局部变量无法使用权限修饰符
* 2.类的属性,依据其类型,具有默认初始化值
* .局部变量没有默认初始化值,在调用之前一定要显式赋值(形参在调用时赋值即可)
* 3.类的属性加载在堆空间
* .局部变量加载在栈空间
*
*/
public class Type {
public static void main(String[] args) {
User u1 = new User();
System.out.println(u1.name); // null
System.out.println(u1.age); // 0
System.out.println(u1.isMale); // false
u1.eat();
u1.talk("英语"); // 形参在这里赋值
}
}
class User{
// 属性(成员变量)
String name;
int age;
boolean isMale;
public void talk(String lang){ // lang为形参,也是局部变量
System.out.println("我可以用" + lang + "进行交流");
}
public void eat(){
String food = "馅饼"; // food也为局部变量
System.out.println("北方人喜欢吃: " + food);
}
}
属性与局部变量的区别
对象属性默认初始化赋值
方法
何为方法(method、函数):
- 方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数或过程。
- 将功能封装为方法的目的是,可以实现代码重用,简化代码。
- Java里的方法不能独立存在,所有的方法必须定义在类里。
比如:
package com.atguigu.java;
/*
* 类中方法的声明和使用
* 声明方式:
* 权限修饰符 + 返回值类型 + 方法名(指定形参 ) {
*
* }
*
* 权限修饰符: public , private , protected , 缺省
* 方法名也属于标识符,遵守标识符的命名规则
* 形参列表:
* 方法名 (数据类型 形参1 , 数据类型 形参2...)
*
*/
public class Method {
public static void main(String[] args) {
Customer cus1 = new Customer();
cus1.eat(); // 客户吃饭
cus1.sleep(8); // 休息了8个小时
cus1.name = "Omew";
System.out.println(cus1.getName()); // Omew
System.out.println(cus1.getNation("China")); // 我的国籍是: China
}
}
class Customer{
// 属性
String name;
int age;
boolean isMale;
// 方法
public void eat(){ // void 无返回值
System.out.println("客户吃饭");
}
public void sleep(int hour){
System.out.println("休息了" + hour + "个小时");
}
public String getName(){ // 返回String类型数据
return name; // 若有返回值,则一定要用 return 关键字
}
public String getNation(String nation){
String info = "我的国籍是: " + nation;
return info;
}
}
注意:
- 方法被调用一次,就会执行一次
- 没有具体返回值的情况,返回值类型用关键字void表示,那么方法体中可以不必使用return语句。如果使用,仅用来结束方法。
- 定义方法时,方法的结果应该返回给调用者,交由调用者处理。
- 方法中只能调用方法或属性,不可以在方法内部定义方法。
对象数组(例题)
例题可以这样写:
// 对象数组***
public class practice3 {
public static void main(String[] args) {
Student[] stu = new Student[20]; // 创建Student类数组
for(int i = 0; i < 20; i++){
stu[i] = new Student(); // 创建Student类的对象
stu[i].number = i + 1;
stu[i].state = (int)(Math.random() * 6 + 1);
stu[i].score = (int)(Math.random() * 101);
}
practice3 pra = new practice3();
pra.printStudent(stu);
System.out.println("***********************************************");
// 获取三年级学生信息
pra.getStateStudent(stu, 3);
System.out.println("***********************************************");
// 按成绩冒泡排序
pra.sortByScore(stu);
pra.printStudent(stu);
}
// 封装函数
// 遍历学生
public void printStudent(Student[] stu){
for(int i = 0; i < stu.length; i++){
stu[i].getStudent();
}
}
// 获取该年级学生信息
public void getStateStudent(Student[] stu, int state){
for(int i = 0; i < stu.length; i++){
if(stu[i].state == state){
stu[i].getStudent();
}
}
}
// 按成绩排序
public void sortByScore(Student[] stu){
Student temp = new Student(); // 注意交换的是整个对象,而不是属性
for(int i = 0; i < stu.length; i++){
for(int j = 0; j < stu.length - i - 1; j++){
if(stu[j].score > stu[j + 1].score){
temp = stu[j];
stu[j] = stu[j + 1];
stu[j + 1] = temp;
}
}
}
}
}
class Student{
int number;
int state;
int score;
public void getStudent(){
System.out.println("学号: " + number + ",年级" + state + ",成绩" + score);
}
}
内存解析
再谈方法
方法重载
image.png例子:
package com.atguigu.java;
/*
* 方法的重载(overload)
* 同一个类中,允许存在一个以上的同名方法,只需要他们的参数个数和参数类型不同即可
*
* 比如,Arrays类中sort() , binarySearch()
*
*
*/
public class methodRebuild {
public static void main(String[] args) {
methodRebuild meth = new methodRebuild();
meth.getSum(1,2); // 3
meth.getSum(3.13,4.13); // 7.26
}
// 以下两个方法为重载关系
public void getSum(int i, int j){
System.out.println(i + j);
}
public void getSum(double d1, double d2){
System.out.println(d1 + d2);
}
}
可变形参的方法
image.pngimage.png
比如:
package com.atguigu.java;
/*
* 可变个数形参的方法(JDK 5.0)
*
*
* 可变形参格式:数据类型 ... 变量名
* 调用时,传入的参数可以是任意个
* 可变个数形参的方法,可以和其它同方法名且 不同参数个数的方法构成重载
* 可变个数形参的方法,不可以和其它同方法名且参数个数为数组的方法构成重载
* 可变个数形参在方法的形参中,必须声明在末尾,且最多只能声明一个
*
*/
public class Arguments {
public static void main(String[] args) {
Arguments arg = new Arguments();
arg.show(2);
arg.show("Hello");
arg.show("Hello","World");
}
public void show(int i){
System.out.println("show(int i)");
}
public void show(String s){
System.out.println("show(String s)");
}
public void show(String ... strs){
System.out.println("show(String ... strs)");
}
// public void show(String[] strs){
//
//
// } // 报错;因为这是JDK5.0以前,可变个数形参方法的写法,实际上和上一个函数重名了
// public void show(String ... strs ,int i){
//
// } // 报错;可变个数形参在方法的形参中,必须声明在末尾,且最多只能声明一个
}
值传递机制(重要)
image.png直接看例子吧:
package com.atguigu.java;
/*
* 方法的形参传递机制:值传递
*
* 1. 形参:方法定义时,声明在小括号内的数据
* 实参:方法调用时,实际传递给形参的数据
*
* 2. 值传递机制
* 如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值
* 如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值
*
*/
public class methodArguments {
public static void main(String[] args) {
//************************基本数据类型*******************************
int m = 10;
int n = 20;
System.out.println("m: "+ m + ",n: " + n);
methodArguments test = new methodArguments(); // m: 10,n: 20
test.exchange(m, n);
System.out.println("m: "+ m + ",n: " + n); // m: 10,n: 20
//************************引用数据类型*******************************
numbers nums = new numbers();
nums.m = 10;
nums.n = 20;
System.out.println("nums.m: "+ nums.m + ",nums.n: " + nums.n); // nums.m: 10,nums.n: 20
test.exchange(nums);
System.out.println("nums.m: "+ nums.m + ",nums.n: " + nums.n); // nums.m: 20,nums.n: 10
}
public void exchange(int m, int n){
int temp = m;
m = n;
n = temp;
}
public void exchange(numbers nums){ // 方法重载
int temp = nums.m;
nums.m = nums.n;
nums.n = temp;
}
}
class numbers{
int m,n;
}
可以看到,如果传入的是普通数据类型,实际上并没有发生值的交换(即传入的是副本,仅仅交换副本并不会影响原始数据);
但是如果传入的是引用数据类型,因为实际上传入引用数据类型都是地址值,所以形参和实参会同时指向这个引用数据类型(比如说堆空间中对象),修改形参就是修改实参
总之记住:
- 如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值
- 如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值
这是绝对不会错的
一道很综合的例题
例题我是这样写的:
package com.atguigu.practice;
/* 1.定义一个Circle类,包含一个double型的radius属性代表圆的半径,一个
findArea()方法返回圆的面积。
* 2.定义一个类PassObject,在类中定义一个方法printAreas(),该方法的定义
如下:public void printAreas(Circle c, int time)
在printAreas方法中打印输出1到time之间的每个整数半径值,以及对应的面积。
例如,times为5,则输出半径1,2,3,4,5,以及对应的圆面积。
*
*/
public class practice1 {
public static void main(String[] args) {
Circle c = new Circle();
PassObject test = new PassObject();
test.printArea(c, 5);
System.out.println("now radius is: " + c.radius);
}
}
class Circle{
double radius;
public double findArea(){
return Math.PI * radius *radius;
}
}
class PassObject{
public void printArea(Circle c ,int time){
System.out.println("Radius" + "\t" + "Area");
for(int i = 1; i <= time; i++){
c.radius = i;
System.out.println(c.radius + "\t" + c.findArea());
}
c.radius ++;
}
}
递归
image.png注:什么叫做往已知方向递归?
比如上面那个求1-100的和,已知情况是当 num = 1 的时候 sum = 1 ;所以在求
当 num = 100 时 sum(100) 的值,会先求的 sum(num - 1) 、即sum( 99 ) 的值;如果想求 sum(99) 的值,就要先求sum(98) 的值... 以此类推,朝着已知条件sum(1) = 1 这个方向前进,这就是朝已知方向递归。
典型例子:
已知有一个数列:f(0) = 1,f(1) = 4,f(n+2)=2*f(n+1) + f(n),其中n是大于0的整数,求f(10)的值。
public int getSeq(int n){
if(n == 0){
return 1;
}else if(n == 1){
return 4;
}else{
return 2 * getSeq(n - 1) + getSeq(n - 2);
}
}
朝已知条件方向f(0) = 1, f(1) = 4前进,只能是用减法推回去
但是如果:
已知一个数列:f(20) = 1,f(21) = 4,f(n+2) = 2*f(n+1)+f(n),其中n是大于0的整数,求f(10)的值。
public int getSeq1(int n){
if(n == 20){
return 1;
}else if(n == 21){
return 4;
}else if(n < 20){
return getSeq1(n + 2) - 2 * getSeq(n + 1);
}else{ // 如果当 n > 20 毫无疑问只能向已知条件的反方向递归了
return 2 * getSeq1(n - 1) + getSeq1(n - 2);
}
}
顺便一提,如果这里试求f(30),那么和上一题f(10)的结果是一样的,都是10497
斐波那契数列:
public int Fibonacci(int n){
if(n == 1){
return 1;
}else if(n == 2){
return 1;
}else{
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
}