Java面向对象编程
一、Java面向对象的三条主线:
1.Java类及类的成员:属性、方法、构造器;代码块、内部类
2.面向对象的三大特征:封装性、继承性、多态性、(抽象性)
3.其他关键字:this、super、static、final、abstract、interface、package、import等
二、“人把大象装进冰箱”
1.面向过程:强调的是功能行为,以函数为最小单位,考虑怎么做。
① 把冰箱打开
② 抬起大象,塞进冰箱
③ 吧冰箱关闭
2.面向对象:强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
人{
打开(冰箱){
冰箱。打开();
}
抬起(大象){
大象.进入(冰箱);
}
关闭(冰箱){
冰箱.闭合();
}
}
冰箱{
打开(){}
关闭(){}
}
大象{
进入(冰箱){}
}
三、面向对象的两个要素:
类:对一类事物得到描述,是抽象、概念上的定义
对象:是实际存在的该类书屋的每个个体,因而也成为实例(instance)
- 面向对象程序设计的重点是类的设计
- 设计类,就是设计类的成员。
1.设计类,就是设计类的成员
属性 = 成员变量 = field = 域、字段
方法 = 成员方法 = 函数 = method
创建类的对象 = 类的实例化 = 实例化类
2.类和对象的使用(面向对象思想落地的实现):
- 创建类,设计类的成员
- 创建类的对象
- 通过“对象.属性”或“对象.方法”调用对象的结构
3.如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static的)
意味着:如果我们修改一个对象的属性a,则不影响另外一个对象的属性a的值。
4.对象的内存解析
堆(Heap):存放对象实例
虚拟机栈(VM Stack):存储局部变量
方法区(Method Area):存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等
//测试类
public class PersonTest{
public static void main(String[] args){
//创建Person类的对象
Person man1 = new Person();
//调用对象的结构:属性、方法
//调用属性:“对象.属性”
man1.name = "Tom";
man1.isMale = true;
System.out.println(man1.name);
//调用方法:“对象.方法”
man1.eat();
man1.sleep();
man1.talk("Chinese");
//*****************************
Person man2 = new Person();
System.out.println(man2.name);//null
//********************************
//将p1变量保存的对象地址值赋给man3,导致man1和man3只想了对空间中的同一个对象实体。
Person man3 = man1;
System.out.println(man3.name);//Tom
man3.age = 10;
System.out.println(man1.age);//10
}
}
class Person{
//属性
String name;
int age = 1;
boolean isMale;
//方法
public void eat(){
System.out.println("人可以吃饭");
}
public void sleep(){
System.out.println("人可以睡觉");
}
public void talk(String language){
System.out.println("人可以说话,使用的是:" + language);
}
}
类中属性的使用
属性(成员变量) vs 局部变量
1.相同点:
1.1 定义变量的格式:数据类型 变量名 = 变量值
1.2 先声明,后使用
1.3 变量都有其对应的作用域
2.不同点:
2.1 在类中声明的位置的不同
属性:直接定义在类的一对{}内
局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内的变量
2.2 关于权限修饰符的不同
属性:可以在声明属性时,致命其权限,使用权限修饰符。
常用的权限修饰符:private、public、缺省、protected —> 封装性
目前:声明权限时,都使用缺省就可以了
局部变量:不可以使用权限修饰符
2.3 默认初始化值的情况:
属性:类的属性,根据其类型,都有默认初始化值。
整型(byte、short、int、long),0
浮点型(float、double),0.0
字符型(char),0(或’\u0000’)
布尔型(boolean),false
引用数据类型(类、数组、接口):null
局部变量:没有默认初始化值。
意味着,我们在调用局部变量之前,一定要显式复制。
特别的:形参在调用时,我们赋值即可。
2.4 在内存中加载的位置:
属性:加载在堆空间中(非static)
局部变量:加载到栈空间
public class UserTest{
public static void main(String[] args){
User u1 = new User();
System.out.println(u1.name);
System.out.println(u1.age);
System.out.println(u1.isMale);
u1.talk("日语");
}
}
class User{
//属性(或成员变量)
String name;
int age;
boolean isMale;
public void talk(String language){//language:形参,也是局部变量
System.out.println("我们使用" + language + "进行交流");
}
public void eat(){
String food = "烙饼";//局部变量
System.out.println("北方人喜欢吃:" + food);
}
}
类中方法的声明和使用
方法:描述类应该具有的功能。
比如:Math类:sqrt()、random() …
Scanner类:nextXxx() …
Arrays类:sort()、binarySearch()、toString()、equals() …
1.举例
public void eat(){}
public void sleep (int hour){}
public String getName(){}
public String getNation(String nation){}
2.方法的声明:权限修饰符 返回值类型 方法名(形参列表){方法体}
注意:static、final、abstract 来修饰的方法,后面再讲
3.说明:
3.1 关于权限修饰符:默认方法的权限修饰符先都使用public
Java规定的4种权限修饰符;private、public、缺省、protected —> 封装性再细说
3.2 返回值类型:有返回值 VS 没有返回值
3.2.1 如果方法有返回值,则必须在方法声明时,指定返回值的类型。同时,方法中,需要使用return关键字来返回指定类型的变量或常量。
如果方法没有返回值,则方法声明时,使用void来表示。通常,没有返回值的方法中,就不需要使用return。但是,如果使用的话,只要“return;”表示结束此方法的意思。
3.2.2 我们定义方法时该不该有返回值?
①题目要求
②凭经验:具体问题具体分析
3.3 方法名:属于标识符,遵循标识符的规则和规范。“见名知意”
3.4 形参列表:方法可以声明0个,1个,或多个形参。
3.4.1 格式:数据类型1 形参1,数据类型2 形参2,…
3.4.2 我们定义方法时:该不该定义形参?
① 题目要求
② 凭经验:具体问题具体分析
3.5 方法体:方法功能的体现。
4.return关键字的使用:
1.使用范围:使用在方法体中
2.作用:① 结束方法
② 针对有返回值类型的方法,使用“return 数据”方法返回索要的数据。
3.return关键字后面不可以声明执行语句。
5.方法的使用中。可以调用当前类的属性或方法
特殊的:方法A中调用了方法A(递归法)
方法中,不可以定义方法。
public class CustomerTest{
public static void main(String[] args){
Customer cust1 = new Customer();
cust1.eat();
cust1.sleep(8);
cust1.isMale = true;
}
}
//客户类
class Customer{
//属性
String name;
int age;
/**
* isMale:true 男性
* isMale:false 女性
*/
boolean isMale;
//方法
public void eat(){
System.out.println("客户吃饭");
}
public void sleep (int hour){
System.out.println("休息了" + hour + "个小时");
eat();
}
public String getName(){
return name;
}
public String getNation(String nation){
String info = "我的国籍是:" + nation;
return info;
}
}
练习
//利用面向对象的编程方法,设计类Circle计算圆的面积
public class CircleTest{
public static void main(String[] args){
Circle c1 = new Circle();
c1.radius = 2.1;
double area = c1.findArea();
System.out.println(area);
}
}
class Circle{
//属性
double radius;
//求圆的面积
public double findArea(){
double area = Math.PI * Math.pow(radius, 2);
return area;
}
}
//在method方法提供m和n两个参数,方法中打印m*n的*矩形,并计算该矩形的面积,将其作为方法的返回值。
import java.util.Scanner;
public class ExerTest{
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
ExerTest test = new ExerTest();
System.out.println("请输入m的值:");
int m = scan.nextInt();
System.out.println("请输入n的值:");
int n = scan.nextInt();
System.out.println(test.method(m, n));
}
public int method(int m,int n){
for(int i = 0;i < m;i++){
for(int j = 0;j < n;j++){
System.out.print("* ");
}
System.out.println();
}
return m * n;
}
}
/*
对象数组题目:
定义类Student,包含三个属性:学号number(int),年级state(int),成绩score(int)。
创建20个学生对象,学号1到20,年级和成绩都由随机数确定。
问题一:打印出3年级(State值为3)的学生信息。
问题二;使用冒泡排序按学生成绩排序,遍历所有学生信息
提示:
1)生成随机数:Math.random(),返回值类型double;
2)四舍五入取整:Math.round(double d),返回值类型long
*/
public class StudentsTest{
public static void main(String[] args){
Student[] stus = new Student[20];
//赋值操作
assignment(stus);
//遍历学生数组
printStus(stus);
//问题一:打印出3年级(State值为3)的学生信息。
System.out.println("============================");
searchState(stus, 3);
//问题二;使用冒泡排序按学生成绩排序,遍历所有学生信息
System.out.println("============================");
bubbleSort(stus);
printStus(stus);
}
/**
*
* @Description 对传入的学生数组按成绩从大到小排序
* @param stus 需要排序的学生数组
*/
public static void bubbleSort(Student[] stus){
for(int i = 0;i < stus.length - 1;i++){
for(int j = 0;j < stus.length - 1 - i;j++){
if(stus[j].score < stus[j + 1].score){
//如果需要换序,交换的是数组的元素:Student对象!!!
Student temp = stus[j];
stus[j] = stus[j + 1];
stus[j + 1] = temp;
}
}
}
}
/**
*
* @Description 给学生数组赋值
* @param stus 要赋值的学生数组
*/
public static void assignment(Student[] stus){
for(int i = 0;i < stus.length;i++){
//给数组元素赋值
stus[i] = new Student();
//给Student对象的属性赋值
stus[i].number = (i + 1);
//年级:[1,6]
stus[i].state = (int)(Math.random() * (6 - 1 + 1) + 1);
//成绩:[0,100]
stus[i].score = (int)(Math.random() * (100 - 0 + 1));
}
}
/**
*
* @Description 打印数组中的学生信息
* @param stus 要打印的学生数组
*/
public static void printStus(Student[] stus){
for(int i = 0;i < stus.length;i++){
System.out.println(stus[i].info());
}
}
/**
*
* @Description 查找Student数组中指定年级的学生信息
* @param stus 要查找的数组
* @param state 要找的年级
*/
public static void searchState(Student[] stus,int state){
for(int i = 0;i < stus.length;i++){
if(stus[i].state == state){
System.out.println(stus[i].info());
}
}
}
}
class Student{
int number;//学号
int state;//年级
int score;//成绩
//显示学生信息的方法
public String info(){
return "学号:" + number + ",年级:" + state + ",成绩:" + score;
}
}
一、理解“完事万物接对象”
1.在Java语言范畴中,我们都将功能、结构等封装到类中,通过类的实例化,来调用具体的功能结构
Scanner,String等
文件:File
网络资源:URL
2.设计到Java语言与前端Html、后端的数据库交互,前后端的结构在Java层面交互时,都体现为类、对象。
二、内存解析的说明
1.引用类型的变量,只可能存储两类值:null 或 地址值(含变量的类型)
三、匿名对象的使用
1.理解:我们创建的对象,没有显式的赋给一个变量名。即为匿名对象。
2.特征:匿名对象只能调用一次
3.使用:如下
public class InstanceTest{
public static void main(String[] args){
Phone p = new Phone();
System.out.println(p);
p.sendEmail();
p.playGame();
//匿名对象
new Phone().sendEmail();
new Phone().price = 1999;
new Phone().showPrice();//0.0
//******************************
//匿名对象的使用
PhoneMall mall = new PhoneMall();
mall.show(new Phone());
}
}
class PhoneMall{
public void show(Phone phone){
phone.sendEmail();
phone.playGame();
}
}
class Phone{
double price;//价格
public void sendEmail(){
System.out.println("发送邮件");
}
public void playGame(){
System.out.println("玩游戏");
}
public void showPrice(){
System.out.println("手机价格为:" + price);
}
}
自定义数组的工具类
//自定义数组的工具类
package ArrayUtilPK;
public class ArrayUtil{
//求数组的最大值
public int getMax(int[] arr){
int maxValue = arr[0];
for(int i = 1;i < arr.length;i++){
if(maxValue < arr[i]){
maxValue = arr[i];
}
}
return maxValue;
}
//求数组的最小值
public int getMin(int[] arr){
return 0;
}
//求数组的总和
public int getSum(int[] arr){
return 0;
}
//求数组的平均值
public int getAvg(int[] arr){
return 0;
}
//反转数组
public void reverse(int[] arr){
}
//复制数组
public int[] copy(int[] arr){
return null;
}
//数组排序
public void sort(int[] arr){
}
//遍历数组
public void print(int[] arr){
}
//查找指定元素
public int getIndex(int[] arr,int dest){
return 0;
}
}
//测试自定义的数组工具类
package ArrayUtilPK;
public class ArrayUtilTest {
public static void main(String[] args){
ArrayUtil util = new ArrayUtil();
int[] arr = new int[]{15,899,55,-125,-111,556,-5,51,0,100};
int max = util.getMax(arr);
System.out.println("最大值为:" + max);
}
}
方法
1.方法的重载(overloead)
1.定义:在同一个类中,允许存在一个以上的同名方法,只要他们的参数个数或者参数类型不同即可
“两同一不同”:同一个类。同一个方法名
参数列表不同:参数个数不同,参数类型不同
2.举例:
Arrays类中重载的sort() / binarySearch()
3.判断是否是重载:
跟方法的权限修饰符、返回值类型、形参变量、方法体都没有关系!
4.在通过对象调用方法时,如何确定某一个指定的方法:
方法名 —> 参数列表
public class OverLoadTest{
public static void main(String[] args){
OverLoadTest test = new OverLoadTest();
test.getSum(1, 2);
}
//如下4个方法构成重载
public void getSum(int i,int j){
System.out.println(i + j);
}
public void getSum(double d1,double d2){
System.out.println(d1 + d2);
}
public void getSum(String s,int i){
}
public void getSum(int i,String s){
}
}
可变个数形参的方法
1.jdk 5.0 新增
2.具体使用:
2.1 可变个数形参的格式:数据类型 … 变量名
2.2 当调用可变个数形参的方法时,传入的参数个数可以是:0个,1个,2个…
2.3 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载
2.4 可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载。
2.5 可变个数形参在方法的形参中,必须声明在末尾
2.6 可变个数形参在方法的形参中,最多只能声明一个可变形参
public class MethodArgsTest{
public static void main(String[] args){
MethodArgsTest test = new MethodArgsTest();
test.show(12);
test.show("hello"," ","world!");
test.show();
}
public void show(int i){
}
public void show(String s){
}
public void show(String ... strs){
for(int i = 0;i < strs.length;i++){
System.out.print(strs[i]);
}
}
}
方法参数的值传递机制
关于变量赋值:
如果变量是基本数据类型,此时赋值的是变量所保存的数据值
如果变量和是引用数据类型,此时赋值的是变量所保存数据的地址值
public class ValueTransferTest{
public static void main(String[] args){
System.out.println("******************基本数据类型******************");
int m = 10;
int n = m;
System.out.println("m = " + m + ",n = " + n);
n = 20;
System.out.println("m = " + m + ",n = " + n);
System.out.println("******************引用数据类型******************");
Order o1 = new Order();
o1.orderId = 1001;
Order o2 = o1;//赋值以后,o1和o2的地址值相同,都指向了堆空间中同一个对象实体
System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " + o2.orderId);
o2.orderId = 1002;
System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " + o2.orderId);
System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " + o2.orderId);
}
}
class Order{
int orderId;
}
方法的形参的传递机制:值传递
1.形参:方法定义时,声明的小括号内的参数
实参:方法调用时,实际传递给形参的数据
2.值传递机制:
如果参数时基本数据类型,此时实参赋给形参的是实参真实存储的数据值。
如果参数时引用数据类型,此时赋值的是变量所保存数据的地址值。
public class ValueTransferTest1{
public static void main(String[] args){
ValueTransferTest1 test = new ValueTransferTest1();
int m = 10;
int n = 20;
System.out.println("m = " + m + ",n = " + n);
//交换两个变量值的操作
int temp = m;
m = n;
n = temp;
//传递的是数值,原值不交换
// test.swap(n, m);
Data data = new Data();
data.m = 10;
data.n = 20;
System.out.println("m = " + m + ",n = " + n);
}
// 错误的交换
// public void swap(int i,int j){
// int temp = i;
// i = j;
// j = temp;
// }
public void swap(Data data){
int temp = data.n;
data.n = data.m;
data.m = temp;
}
}
class Data{
int m;
int n;
}
递归方法的使用
1.递归方法:一个方法内调用它自己
2.方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
public class RecursionTest{
public static void main(String[] args){
RecursionTest test = new RecursionTest();
//计算1-100之间所有的自然数之和
System.out.println(test.getSum(100));
//已知一个数列:f(0) = 1,f(1) = 4,f(n + 2) = 2 * f(n + 1) + f(n),求f(10)
System.out.println(test.f(10));
}
public int getSum(int n){
if(n == 1)return 1;
return n + getSum(n - 1);
}
public int f(int n){
if(n == 0)return 1;
if(n == 1)return 4;
return 2 * f(n - 1) + f(n - 2);
}
}
面向对象的特征一:分装与隐藏
思想:把该隐藏的隐藏起来,该暴露的暴露出来
一、问题的引入:
当我们创建一个类的对象以后,我们可以通过“对象.属性”的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没有其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。(比如,setLegs())
同时,我们需要避免用户再使用“对象.属性”的方式对属性进行赋值。则需要讲属性声明为私有的(private)–> 此时,针对于属性就体现了封装性
二、封装性的体现:
我们将类的属性私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(SetXxx)
拓展:分装性的体现:① 如上 ② 不对外暴露的私有的方法 ③ 单例模式 …
三、封装性的体现,需要权限修饰符来配合
1.Java规定的4种权限(从小到大排列):private、缺省、protected、public
2.4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类
3.具体的,4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类
修饰类的话,只能使用:缺省、public
总结封装性:Java提供了4种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小。
public class AnimalTest{
public static void main(String[] args){
Animal a = new Animal();
a.name = "大黄";
a.age = 1;
a.legs = 4;
a.show();
a.setLegs(4);
}
}
class Animal{
String name;
int age;
private int legs;//腿的个数
//对属性的设置
public void setLegs(int l){
if(l >= 0 && l % 2 == 0){
legs = l;
}else{
legs = 0;
//抛出一个异常(暂时没讲)
}
}
//对属性的获取
public int getLegs(){
return legs;
}
public void eat(){
System.out.println("动物进食");
}
public void show(){
System.out.println("name = " + name + ",age = " + age + ",legs = " + legs);
}
}
类的构造之三:构造器(或构造方法、constructor) 的使用
Constructor:建设、建造。
一、构造器的作用:
1.创建对象
2.初始化对象的属性
二、说明:
1.如果没有显式的定义构造器的话,则系统默认提供一个空参的构造器
2.定义构造器的格式:权限修饰符 类名(形参列表)
3.一个类中定义的多个构造器,彼此构成重载
4.一旦我们显式的定义了类的构造器之后,系统就不再提供默认的空参构造器
5.一个类中,至少有一个构造器
总结:属性赋值的先后顺序
① 默认初始化
② 显式初始化
③ 构造器初始化
④ 通过“对象.方法” 或 “对象.属性”的方法赋值
以上操作的先后顺序:① - ② - ③ - ④
public class PersonTest2{
public static void main(String[] args){
//创建类的对象:new + 构造器
Person2 p = new Person2();
Person2 p2 = new Person2("Tom");
System.out.println(p2.name);
}
}
class Person2{
//属性
String name;
int age;
//构造器
public Person2(){
System.out.println("Person2()...");
}
public Person2(String n){
name = n;
}
public Person2(String n,int a){
name = n;
age = a;
}
public void eat(){
System.out.println("吃饭");
}
}
JavaBean是一种Java语言携程的可重用组件
所谓JavaBean,是指符合如下标准的java类:
1.类是公共的
2.有一个无参的公共的构造器
3.有属性,且有对应的get、set方法
UML图
1.+表示public类型,-表示private类型,#表示protected类型
2.方法的写法:
方法的类型(+、-) 方法名(参数名:参数类型):返回值类型
this的使用
1.this可以用来修饰、调用:属性、方法、构造器
2.this修饰属性和方法:
this理解为:当前对象 或 当前正在创建的对象
2.1在类的方法中,我们可以使用“this.属性”或“this.方法”的形式,调用当前对象属性或方法。但是,通常情况下,我们都选择省略“this.”。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用“this.变量”的方式,表明此变量是属性,而非形参。
3.this调用构造器
① 我们在类的构造器中,可以显式的使用“this(形参列表)”方式。调用本类中指定的其他构造器
② 构造器中不能用过“this(形参列表)”方式调用自己
③ 如果一个类中有n个构造器,则最多有n - 1构造器中使用了“this(形参列表)”
④ 规定:“this(形参列表)”必须声明在当前构造器的首行
⑤ 构造器内部,最多只能声明一个“this(形参列表)”来调用其他构造器
package PK2;
public class PersonTest{
public static void main(String[] args){
Person p1 = new Person();
p1.setAge(1);
System.out.println(p1.getAge());
Person p2 = new Person("Tom",18);
System.out.println(p2.getName() + "," + p2.getAge());
}
}
class Person{
private String name;
private int age;
public Person(){
}
public Person(String name){
this();//调空参构造器
this.name = name;
}
public Person(int age){
this();
this.age = age;
}
public Person(String name,int age){
this(age);
this.name = name;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
public void eat(){
System.out.println("吃饭");
this.study();
}
public void study(){
System.out.println("学习");
}
}
关键字:package 和 import
一、package关键字的使用
1.为了更好的实现项目中类的管理,提供包的概念
2.使用package声明类或接口所属的包,声明在源文件的首行
3.包,属于标识符,遵循标识符的命名规则、规范(xxxyyyzzz)“见名知意”
4.每“.”一次,就代表一层文件目录
补充:同一个包下不能命名同名的接口、类。
不同的包下可以命名同名的接口、类
二、import关键字的使用
import:导入
1.在源文件中显式的使用import结构导入指定包下的类、接口
2.声明在包的声明和类的声明之间
3.如果需要导入多个结构,则并列写出即可
4.使用“XXX.”的方式,表示可以导入xxx包下的所有结构
5.如果使用的类或接口是java.lang包下定义的,则可以省略import结构
6.如果使用的类或接口是本包下定义的,则可以省略import结构
7.如果在源文件中,使用了不同包下的类,则必须至少有一个雷需要以全类名的方式显示
8.如果使用“XXX.”方式表明可以调用XXX包下的所有结构。但是如果使用的是XXX子包下的结构,则仍需要显式导入
9.import static:导入指定类或接口中的静态结构:属性、方法
MVC的设计模式
将整个程序分为三个层次:视图模型层,控制器层,数据模型层