前言
第二部分(191-423) 是面向对象编程,集中学习这一部分。
参考资料:https://www.bilibili.com/video/BV1fh411y7R8p=192&vd_source=7b719da0e4965c852fae45929d82a5b7
1. 面向对象编程(基础部分)(191-262)周六
类与对象
案例:
养猫问题:看一个养猫猫问题 张老太养了两只猫猫:一只名字叫小白,今年 3 岁,白色。还有一只叫小花,今年 100 岁,花色。请编写一个程序,当用户 输入小猫的名字时,就显示该猫的名字,年龄,颜色。如果用户输入的小猫名错误,则显示 张老太没有这只猫猫。
使用现有技术解决:
1)单独的定义变量解决
2)使用数组解决
现有技术的缺点分析:
1)不利于数据的管理
2)效率低=> 引出新知识 类与对象
类与对象的关系示意图:
package chapter06;
public class Object01 {
public static void main(String[] args){
//使用OOP面向对象解决
//实例化一只猫
// 1. new Cat() 创建一只猫
// 2. Cat cat1 = new Cat(); 把创建的猫赋给cat1
Cat cat1 = new Cat();
cat1.name = "小白";
cat1.age = 3;
cat1.color = "白色";
Cat cat2 = new Cat();
cat2.name = "小黑";
cat2.age = 4;
cat2.color = "黑色";
//怎么访问对象的属性
System.out.println("第一只猫的信息:"+cat1.name+" "
+cat1.age+" "+cat1.color);
System.out.println("第二只猫的信息:"+cat2.name+" "
+cat2.age+" "+cat2.color);
}
}
//定义一个猫类 一个数据类型
class Cat{
//属性
String name;
int age;
String color;
}
注意事项:
- 类是抽象的,概念的,代表一类事物,比如人类,猫类…, 即它是数据类型.
- 对象是具体的,实际的,代表一个具体事物, 即 是实例.
- 类是对象的模板,对象是类的一个个体
对象在内存中存在形式:
属性/成员变量/字段
1)从概念或叫法上看: 成员变量 = 属性 = field(字段) (即 成员变量是用来表示属性的,授课中,统一叫 属性)
2)属性是类的一个组成部分,一般是基本数据类型,也可是引用类型(对象,数组)。比如我们前面定义猫类 的 int age 就 是属性
注意事项:
1)属性的定义语法同变量,示例:访问修饰符 属性类型 属性名;
这里老师简单的介绍访问修饰符: 控制属性的访问范围 有四种访问修饰符 public, proctected, 默认, private ,后面我会详细介绍
2) 属性的定义类型可以为任意类型,包含基本类型或引用类型
3) 属性如果不赋值,有默认值,规则和数组一致。具体说: int 0,short 0, byte 0, long 0, float 0.0,double 0.0,char \u0000, boolean false,String null
package chapter06;
public class PropertiesDetail {
public static void main(String[] args){
//创建Person对象
Person person1 = new Person();
System.out.println("当前这个人的信息:");
System.out.println("age="+person1.age+" name="+person1.name+
" sal="+person1.sal+" isPass="+person1.isPass);
}
}
class Person{
int age;
String name;
double sal;
boolean isPass;
}
如何访问对象:
- 先声明再创建
Cat cat ; //声明对象
cat cat = new Cat(); //创建 - 直接创建
Cat cat = new Cat();
如何访问属性:
基本语法
对象名.属性名;
案例演示赋值和输出
cat.name ;
cat.age;
cat.color;
思考题:
p2的内存指向p1
Java 内存的结构分析
- 栈: 一般存放基本数据类型(局部变量)
- 堆: 存放对象(Cat cat , 数组等)
- 方法区:常量池(常量,比如字符串), 类加载信息
Java 创建对象的流程简单分析
Person p = new Person();
p.name = “jack”;
p.age = 10;
1)先加载Person类信息(属性和方法信息,只会加载一次)
2)在堆中分配空间,进行默认初始化(看规则)
3)把地址赋给p,p 就指向对象
4)进行指定初始化,比如p.name=“jack” p.age=10
思考题:
成员方法(209)
在某些情况下,我们要需要定义成员方法(简称方法)。比如人类:除了有一些属性外( 年龄,姓名…),我们人类还有一 些行为比如:可以说话、跑步…,通过学习,还可以做算术题。这时就要用成员方法才能完成。现在要求对 Person 类完善。
快速入门版:
package chapter06;
public class Method01 {
public static void main(String[] args){
//方法使用
//注意:不能在同一个Package里有多个class 里拥有同样名字的类
//1. 方法写好后,如果不去调用,不会输出
//2. 先创建对象,然后调用方法即可
Personx p1 = new Personx();
p1.speak();
p1.cal01();
p1.cal02(3);
int sum = p1.getSum(2, 3);
System.out.print("getSum方法返回的值="+sum);
}
}
class Personx{
int age;
String name;
//方法(成员方法)
//添加 speak 成员方法,输出 “我是一个好人”
//1. public 表示方法是公开
//2. void : 表示方法没有返回值
//3. speak() : speak 是方法名, () 形参列表
//4. {} 方法体,写我们要执行的代码
public void speak(){
System.out.println("我是一个好人");
}
//添加 cal01 成员方法,可以计算从 1+..+1000
public void cal01(){
int res = 0;
for(int i =1;i<=1000;i++){
res += i;
}
System.out.println("cal01方法 计算结果="+res);
}
//添加 cal02 成员方法,该方法可以接收一个数 n,计算从 1+..+n 的结果
public void cal02(int n){
int res = 0;
for(int i =1;i<=n;i++){
res += i;
}
System.out.println("cal02方法 计算结果="+res);
}
//添加 getSum 成员方法,可以计算两个数的和
//1. public 表示方法是公开的
//2. int :表示方法执行后,返回一个 int 值
//3. getSum 方法名
//4. (int num1, int num2) 形参列表,2 个形参,可以接收用户传入的两个数
//5. return res; 表示把 res
public int getSum(int num1, int num2){
int res;
res = num1+num2;
return res;
}
}
方法调用机制原理:
为什么需要成员方法?
将多次使用的功能写到一个类的方法中,提高效率。
- 提高代码的复用性
- 可以将实现的细节封装起来,然后供其他用户来调用即可
成员方法的定义:
访问修饰符 返回数据类型 方法名(形参列表..) {//方法体
语句;
return 返回值;
}
- 形参列表:表示成员方法输入 cal(int n) , getSum(int num1, int num2)
- 返回数据类型:表示成员方法输出, void 表示没有返回值
- 方法主体:表示为了实现某一功能代码块
- return 语句不是必须的。
注意事项和细节:
package chapter06;
public class MethodDetail {
public static void main(String[] args){
AA a = new AA();
int[] res = a.getSumAndSub(3, 2);
System.out.println("和="+res[0]);
System.out.println("差="+res[1]);
细节: 调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数
byte b1 = 1;
byte b2 = 2;
a.getSumAndSub(b1, b2); // byte->int
// a.getSumAndSub(1.1, 1.8) // double->int (x)
//细节: 实参和形参的类型要一致或兼容、个数、顺序必须一致
//a.getSumAndSub(100) // x 个数不一致
a.f3("tom", 10);// ok
// a.f3(100,"jack"); //实际参数和形式参数顺序不对
}
}
class AA{
// 细节:方法不能嵌套定义
public void f4(){
//错误
// public void f5(){
// }
}
public void f3(String str,int n){
}
//1. 一个方法最多有一个返回值(返回多个结果=》返回数组)
public int[] getSumAndSub(int n1,int n2){
int[] resArr = new int[2];
resArr[0]=n1+n2;
resArr[1]=n1-n2;
return resArr;
}
//2.返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
// 如上所示getSumAndSub
//3.如果方法要求返回数据类型,则方法中最后执行语句必须为return值
// 而且要求返回值类型必须和return的值 类型一致或兼容
public double f1(){
double d1 = 1.1 * 3;
int n =100;
return n;//int -> double
// return d1; // ok? double->int (x)
}
//如果方法是void,则方法体中可以没有return语句,或者只写return
// 在实际工作中,我们的方法都是为了完成某个功能,所以方法名要有一定含义
// 最好见名知意
public void f2(){
System.out.println("hello1");
System.out.println("hello1");
System.out.println("hello1");
int n = 10;
// return ;
}
}
- 访问修饰符(作用是控制 方法使用的范围)
如果不写默认访问,[有四种:public,protected,默认,private] - 返回数据类型
- 一个方法最多有一个返回值 [思考,如何返回多个结果 返回数组 ]
- 返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
- 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 值; 而且要求返回值类型必须和 return 的 值类型一致或兼容
- 如果方法是 void,则方法体中可以没有 return 语句,或者 只写 return
-
方法名
遵循驼峰命名法,最好见名知义,表达出该功能的意思即可, 比如 得到两个数的和 getSum, 开发中按照规 -
形参列表
-
方法体
-
方法调用细节说明:
package chapter06;
public class MethodDetail02 {
public static void main(String[] args){
A a = new A();
a.sayOk();
a.m1();
}
}
class A{
public void print(int n ){
System.out.println("print()方法被调用 n="+n);
}
public void sayOk(){ //sayOk调用print(直接调用即可)
print(10);
System.out.println("继续执行sayOk()~~~~");
}
//跨类中的方法A类调用B类方法:需要通过对象名调用
public void m1(){
System.out.println("m1()方法被调用");
B b = new B();
b.hi();
System.out.println("m1()继续执行");
}
}
class B{
public void hi(){
System.out.println("B类中的hi()被执行");
}
}
成员方法传参机制
基本数据类型的传参机制
结论:基本数据类型传递的是值(值拷贝),形参的任何拷贝不影响实参。
引用数据类型的传参机制
结论:引用类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参!
思考题:方法的p=null
不改变main中p的值,只是方法中p的指针不指向main的p,因此p的输出还是10。
思考题:p=new Person()
方法中新建一个类,此时会创建一个新的堆,方法中的p指向新堆,并不改变main的堆。但是main中输出的还是main中的p,并不改变值,p.age仍然等于10。
应用实例:
- 编写类 MyTools 类,编写一个方法可以打印二维数组的数据。
- 编写一个方法 copyPerson,可以复制一个 Person 对象,返回复制的对象。克隆对象, 注意要求得到新对象和原来的对象是两个独立的对象,只是他们的属性相同
package chapter06;
public class MethodExercise02 {
public static void main(String []args){
// 1) 编写类 MyTools 类,编写一个方法可以打印二维数组的数据。
// 2) 编写一个方法 copyPerson,可以复制一个 Person 对象,返回复制的对象。
// 克隆对象, 注意要求得到新对象和原来的对象是两个独立的对象,只是他们的属性相同
int[][] arr = {{1,2,3},{34,34,6}};
MyTools m = new MyTools();
m.output(arr);
Person p = new Person();
p.name="jack";
p.age=10;
Person p2 = m.copyPerson(p);
System.out.println("person p ="+p.name+" "+p.age);
System.out.println("person p2 ="+p2.name+" "+p2.age);
//判断对象是否为同一个
System.out.print(p==p2); // false
}
}
class MyTools{
public int[][] output(int[][] arr){
for(int i=0;i<arr.length;i++){
for(int j=0;j<arr[i].length;j++){
System.out.print(arr[i][j]+" ");
}
System.out.println();
}
return arr;
}
public Person copyPerson(Person p){
Person p2 = new Person();
p2.name = p.name;
p2.age = p.age;
return p2;
}
}
class Person{
String name;
int age;
}
方法递归调用
递归就是方法自己调用自己,每次调用时传入不同的变量。
递归重要规则:
1)执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
2)方法的局部变量是独立的,不会相互影响,比如n变量
3)如果方法中使用的是引用类型变量(比如数组,对象),就会共享该引用类型的数据
4)递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError
5)当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁。同时,当方法执行完毕或返回时,该方法也就执行完毕。
课堂练习:
1)使用递归实现斐波那契数1,1,2,3,5,8,13…给你一个整数n,求出它的值是多少
2)猴子吃桃问题:有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一半,然后再多吃一个。当到第10天时,想再吃时(即还没吃),发现只有1个桃子了。问题:最初共有多少个桃子?
package chapter06;
public class RecursionExercise01 {
public static void main(String[] args) {
// 1)使用递归实现斐波那契数1,1,2,3,5,8,13....
// 给你一个整数n,求出它的值是多少
T t = new T();
int res = t.fibonacci(7);
System.out.println("fibonacci = "+res);
// 2)猴子吃桃问题:有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!
// 以后每天猴子都吃其中的一半,然后再多吃一个。
// 当到第10天时,想再吃时(即还没吃),发现只有1个桃子了。
// 问题:最初共有多少个桃子?
int tao = t.tao(1);
System.out.println("一共"+tao+"个桃子。");
}
}
class T{
public int fibonacci(int n){
// 3=2+1;5=3+2;....
if(n>=1){
if(n==1||n==2){
return 1;
}else{
return fibonacci(n-1)+fibonacci(n-2);
}
}else{
System.out.println("请输入>1的整数");
return -1;
}
}
public int tao(int day){
// day10, tao=1
// day9, tao=(day10+1)*2
// day8, tao=(day9+1)*2
// tao=(后一天的tao+1)*2
if(day == 10){
return 1;
}else if(day>=1 && day<=9){
return((tao(day+1)+1)*2);
}else{
System.out.println("请输入1-10的数字");
return -1;
}
}
}
方法重载(overload)(232)
java中允许同一个类中,多个同名方法的存在,但要求形参列表不一致!
重载的好处:
1)减轻了起名的麻烦
2)减轻了记名的麻烦
案例:类:MyCalculator 方法:calculate
- calculate(int n1, int n2) //两个整数的和
- calculate(int n1, double n2) //一个整数,一个 double 的和
- calculate(double n2, int n1)//一个 double ,一个 Int 和
- calculate(int n1, int n2,int n3)//三个 int 的
package chapter06;
public class OverLoad01 {
public static void main(String[] args){
MyCalculator mycal = new MyCalculator();
System.out.println(mycal.calculator(1, 1.8));
}
}
class MyCalculator{
public int calculator(int n1,int n2){
int res = n1 + n2;
return res;
}
public double calculator(int n1,double n2){
double res = n1 + n2;
return res;
}
public double calculator(double n1,int n2){
double res = n1 + n2;
return res;
}
public int calculator(int n1,int n2,int n3){
int res = n1 + n2 + n3;
return res;
}
}
注意事项和使用细节:
1)方法名:必须相同
2)形参列表:必须不同(形参类型或个数、顺序,至少有一样不同,参数名无要求)
3)返回类型:无要求
可变参数
java允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。
基本语法:
访问修饰符 返回类型 方法名(数据类型... 形参名){
}
快速入门案例:
看一个案例 类 HspMethod,方法 sum 【可以计算 2 个数的和,3 个数的和 , 4. 5, 。。
package chapter06;
public class VarParameter01 {
public static void main(String[] args){
HspMethod s = new HspMethod();
System.out.print(s.sum(1,2,5));
}
}
class HspMethod{
public int sum(int n1,int n2,int n3){
return n1+n2+n3;
}
public int sum(int n1,int n2,int n3,int n4){
return n1+n2+n3+n4;
}
// ... 方法名称相同,功能相同,参数个数不同 -> 使用可变参数优化
// 1. int... 表示接受的是可变参数,类型是int,即可以接收多个int(0-多)
// 2. 使用可变参数时,可以当做数组来使用 即 nums 可以当做数组
public int sum(int... nums){
System.out.print("接收的参数个数="+nums.length);
int res = 0;
for(int i =0;i<nums.length;i++){
res+=nums[i];
}
return res;
}
}
作用域
注意事项和细节使用:
构造方法/构造器
基本语法:
[修饰符] 方法名(形参列表){
方法体;
}
- 构造器的修饰符可以默认, 也可以是 public protected private
- 构造器没有返回值
- 方法名 和类名字必须一样
- 参数列表 和 成员方法一样的规则
- 构造器的调用, 由系统完成
构造方法又叫构造器,是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。
特点如下:
1)方法名和类名相同
2)没有返回值,也不能写void
3)在创建对象时,系统会自动的调用该类的构造器完成对象的初始化。
package chapter06;
public class Constructor01 {
public static void main(String[] args){
Pperson p1 = new Pperson("smith", 80);
System.out.println("p1对象name="+p1.name);
System.out.println("p1对象age="+p1.age);
}
}
class Pperson{
String name;
int age;
public Pperson(String pName,int pAge){
System.out.println("构造器被调用~ 完成对象的属性初始化");
name = pName;
age = pAge;
}
}
注意事项和使用细节:
对象创建的流程分析
this关键字
引出:如果构造器的形参能够直接使用属性名就好了
java虚拟机会给每个对象分配this,代表当前对象。
小结:哪个对象调用,this就代表哪个对象
package chapter06;
public class This {
public static void main(String[] args){
Dog dog1 = new Dog("小黄",11);
System.out.println("this.hashcode="+dog1.hashCode());
dog1.info();
Dog dog2 = new Dog("小白", 20);
System.out.println("this.hashcode="+dog2.hashCode());
dog2.info();
}
}
class Dog{
String name;
int age;
public Dog(String name, int age){
this.name = name;
this.age = age;
System.out.println("this.hashcode="+this.hashCode());
}
public void info(){
System.out.println("this.hashCode="+this.hashCode());
System.out.println(name+"\t"+age+"\t");
}
}
this的注意事项和细节说明:
1)this关键字可以用来访问本类的属性、方法、构造器
2)this用于区分当前类的属性和局部变量
3)访问成员方法的语法:this.方法名(参数列表);
4)访问构造器的语法:this(参数列表); 注意只能在构造器中使用(即只能在构造器中访问另一个构造器,必须放在第一条语句)
5)this不能在类定义的外部使用,只能在类定义的方法中使用
本章作业
我只写了一个,前面的题目想了一下思路。这个题的代码思路来自与自己,和老师讲的差不多,界面上面有一些区别。
package chapter06;
import java.security.PublicKey;
import java.util.Random;
import java.util.Scanner;
public class Homewotk14 {
public static void main(String[] args){
// 14. Tom设计他的成员变量.成员方法,可以电脑猜拳
// 电脑每次都会随机生成0,1,2
// 0表示石头 1表示剪刀 2表示布
// 并要可以显示Tom的输赢次数(清单)
Tom t = new Tom();
t.shwoInfo(4);
}
}
class Tom{
int tomGuessNum;
int comGuessNum;
int winCountNum;
public int comGuess(){
Random r = new Random();
int comGuessNum = r.nextInt(2); // 随机生成0-2
System.out.println("电脑输出:"+comGuessNum);
return comGuessNum;
}
public int tomGuess(){
Scanner myScanner = new Scanner(System.in);
System.out.println("请输入:0/1/2, 其中0表示石头 1表示剪刀 2表示布");
int tomGuessNum = myScanner.nextInt(); //用户输入
if(tomGuessNum==0 || tomGuessNum==1 || tomGuessNum==2){
return tomGuessNum;
}else{
System.out.println("输入错误");
return -1;
}
}
public String vsCom(){ // 对局信息
int tomGuessNum = tomGuess();
int comGuessNum = comGuess();
if((tomGuessNum==0&&comGuessNum==1)||(tomGuessNum==1&&comGuessNum==2)||(tomGuessNum==2&&comGuessNum==0)){
winCountNum+=1;
return "Tom赢";
}else if((tomGuessNum==2&&comGuessNum==1)||(tomGuessNum==0&&comGuessNum==2)||(tomGuessNum==1&&comGuessNum==0)){
return "电脑赢";
}else{
return "平手";
}
}
public void shwoInfo(int n){
for(int i=0;i<n;i++){
String res = vsCom();
System.out.println(res+"!");
}
System.out.println("对局"+n+"次,"+"Tom一共赢了"+winCountNum+"次");
}
}
2. 面向对象编程(中级部分)(263-359)
包
包的三大作用:
1)区分相同名字的类
2)当类很多时,更好的管理(Java API文档)
3)控制访问范围
包的基本语法:package com.hspedu;
说明:
- package 关键字,表示打包
- com.kspedu; 表示包名
包的本质:实际上就是创建不同的文件夹/目录来保存类文件
快速入门:
在src下面新建两个包,包里面都含有Dog类,在Test.java调用的情况如下:
包的命名
命名规则:只能包含数字、字母、下划线、小圆点.,但不能用数字开头,不能是关键字或保留字
命名规范:一般是小写字母+小圆点,比如com.公司名.项目名.业务模块名 (com.sina.crm.user //用户模块)
常用的包
一个包下,包含很多的类,java 中常用的包有:
- java.lang.* //lang 包是基本包,默认引入,不需要再引入.
- java.util.* //util 包,系统提供的工具包, 工具类,使用 Scanner
- java.net.* //网络包,网络开发
- java.awt.* //是做 java 的界面开发,GUI
如何引用? 语法:import 包;
引入一个包的主要目的是要使用该包下面的类,比如import java.util.Scanner; 就只是引入一个类Scanner,import java.util.*;//表示引入Java.util包的所有都引入
建议:使用到哪个类,就导入哪个类,不建议使用*导入。
注意事项和使用细节:
- package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package
- import指令位置放在package的下面,在类定义前面,可以有多句且没有顺序要求
package chapter07; //package 声明当前类所在的包
// import指令
import java.util.Scanner;
// 类定义
public class Test{
}
访问修饰符
基本介绍:java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):
- 公开级别:用 public 修饰,对外公开
- 受保护级别:用 protected 修饰,对子类和同一个包中的类公开
- 默认级别:没有修饰符号,向同一个包的类公开.
- 私有级别:用 private 修饰,只有类本身可以访问,不对外公开.
使用的注意事项:
1)修饰符可以用来修饰类中的属性,成员方法以及类
2)只有默认的和public才能修饰类!并且遵循上述访问权限的特点。
3)因为没有学习集成,因此关于在子类中的访问权限,后续再 讲解
4)成员方法的访问规则和属性完全一样。
A.java
package com.hspedu.modifier;
public class A {
//四个属性,分别使用不同的访问修饰符来修饰
public int n1 = 100;
protected int n2 = 200;
int n3 = 300;
private int n4 = 400;
public void m1(){
//在同一类中,可以访问public protected 默认 provate修饰属性和方法
System.out.println("n1="+n1+" n2="+n2+" n3="+n3+" n4="+n4);
}
public void m2(){}
void m3(){}
private void m4(){}
public void hi(){
//在同一类中,可以访问public protected 默认 provate修饰属性和方法
m1();
m2();
m3();
m4();
}
}
B.java
package com.hspedu.modifier;
public class B {
public void say(){
A a = new A();
//在同一个包下,可以访问public,protected和默认,不能访问private
System.out.println("n1="+a.n1+" n2="+a.n2+" n3="+a.n3);
}
}
package com.hspedu.pkg;
import com.hspedu.modifier.A;
public class Test {
public static void main(String[] args){
A a = new A();
//在不同包下,可以访问public修饰的属性或方法
System.out.println(a.n1);
// 不能访问 protected 默认和private修饰的属性和方法
}
}
package com.hspedu.modifier;
public class Test {
public static void main(String[] args){
A a = new A();
a.m1();
a.hi();
B b = new B();
b.say();
}
}
// 只有默认和public可以修饰类
class Tiger{
}
面向对象编程三大特征
面向对象编程有三大特征:封装、继承和多态
封装介绍
封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其它部分只有通过被授权的操作[方法],才能对数据进行操作。
封装的理解和好处
1)隐藏实现细节:方法(连接数据库)<–调用(传入参数…)
2)可以对数据进行验证,保证安全合理
Person {name,age}
Person p = new Person();
p.name="jack";
p.age=1200;
封装的实现步骤(三步)
快速入门案例(284)
案例:不能随便查看人的年龄,工资等隐私,并对设置的年龄进行合理的验证。年龄合理就设置,否则给默认年龄,必须再1-120,年龄和工资不能直接查看,name的长度在2-6字符之间。
package com.hspedu.encap;
public class Encapsulation01 {
public static void main(String[] args) {
Person person = new Person();
person.setName("jack");
person.setAge(30);
person.setSalary(10000);
System.out.println(person.info());
}
}
class Person {
public String name;// 姓名公开
private int age; // 年龄和工资私有化
private double salary;
public Person(){
}
public Person(String nam,int age,double salary){
// this.name=name;
// this.age=age;
// this.salary=salary;
// 将set方法写在构造器中,仍然可以进行验证
setName(nam);
setAge(age);
setSalary(salary);
}
// 自己写setXXX getXXX太慢,使用快捷键生成
// 然后根据要求来完善代码
public String getName() {
return name;
}
public void setName(String name) {
if(name.length()>=2 && name.length()<=6){
this.name = name;
}else{
System.out.println("名字的长度不对,需要2-6个字符,默认名字");
this.name="Null";
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age<=120 && age>=1){
this.age = age;
}else{
System.out.println("年龄设置错误,需要在1-120之间,默认年龄18");
this.age=18;
}
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
//写一个方法,返回属性信息
public String info(){
return "信息为 name="+name+ " age="+age+" 薪水="+salary;
}
}
构造器和setXXX结合: 详细请看上图
public Person(String nam,int age,double salary){
// this.name=name;
// this.age=age;
// this.salary=salary;
// 将set方法写在构造器中,仍然可以进行验证
setName(nam);
setAge(age);
setSalary(salary);
}
面向对象编程-继承
为什么需要继承?两个类的属性和方法有很多相同的,则需要继承(代码复用性)。
当多个类存在相同的属性(变量)和方法时,可以抽象出父类,在父类中定义这些相同的属性和方法,子类不需要重新定义这些属性和方法,只需要通过 extends 来 声明继承父类即可。
继承带来了1)代码的复用性提高;2)代码的扩展性和维护性的提高
继承示意图:
继承的基本语法:
class 子类 extends 父类{
}
1)子类就会自动拥有父类定义的属性和方法
2)父类又叫超类,基类
3)子类又叫派生类
快速入门案例
父类
package com.hspedu.extend_.improve_;
// 父类,是Pupil和Graduate的父类
public class Student {
// 共有属性
public String name;
public int age;
private double score; //成绩
//共有的方法
public void setScore(double score) {
this.score = score;
}
public void showInfo(){
System.out.println("学生名 "+name+" 年龄 "+age+" 成绩 "+score);
}
}
子类1
package com.hspedu.extend_.improve_;
// 让Pupil继承Student
public class Pupil extends Student {
public void testing(){
System.out.println("小学生"+name+"正在考数学..");
}
}
子类2
package com.hspedu.extend_.improve_;
public class Graduate extends Student{
public void testing(){
System.out.println("大学生"+name+"正在考数学..");
}
}
测试代码:
package com.hspedu.extend_.improve_;
public class Extends01 {
public static void main(String[] args){
Pupil p = new Pupil();
p.name = "jay";
p.testing();
p.setScore(50);
p.showInfo();
}
}
继承的深入讨论/细节问题
1)子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
2) 子类必须调用父类的构造器, 完成父类的初始化
3) 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器。如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。
4) 如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)
5) super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)
6) super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
7) java 所有类都是 Object 类的子类, Object 是所有类的基类.
8)父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)
9)子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制。 思考:如何让 A 类继承 B 类和 C 类? 【A 继承 B, B 继承 C】
10) 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
子类创建的内存布局:
当子类创建好后,建立查找的关系(先看子类是否有该属性->子类有则直接返回信息,如果没有查看弗雷是否有该属性->如果父类有则返回信息,如果没有继续查找上级父类直到Object…)
课堂练习:
案例:
编写 Computer 类,包含 CPU、内存、硬盘等属性,getDetails 方法用于返回 Computer 的详细信息
编写 PC 子类,继承 Computer 类,添加特有属性【品牌 brand】
编写 NotePad 子类,继承 Computer 类,添加特有属性【color】
编写 Test 类,在 main 方法中创建 PC 和 NotePad 对象,分别给对象中特有的属性赋值,以及从 Computer 类继承的 属性赋值,并使用方法并打印输出信息
Computer类
package com.hspedu.extend_.exercise;
public class Computer {
// 编写 Computer 类,包含 CPU、内存、硬盘等属性,
// getDetails 方法用于返回 Computer 的详细信息
private String cpu;
private int memory;
private int disk;
public Computer(String cpu,int memory,int disk){
this.cpu = cpu;
this.memory = memory;
this.disk = disk;
}
public String getDetails(){
return "cpu:"+cpu+" memory: "+memory+"disk:"+disk;
}
public String getCpu() {
return cpu;
}
public void setCpu(String cpu) {
this.cpu = cpu;
}
public int getMemory() {
return memory;
}
public void setMemory(int memory) {
this.memory = memory;
}
public int getDisk() {
return disk;
}
public void setDisk(int disk) {
this.disk = disk;
}
}
PC子类
package com.hspedu.extend_.exercise;
public class PC extends Computer {
// 编写PC 子类,继承 Computer 类,添加特有属性【品牌 brand】
private String brand;
public PC(String cpu,int memory,int disk,String brand){
super(cpu, memory, disk);
this.brand=brand;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public void printInfo(){
System.out.println("PC信息如下:");
System.out.println(getDetails()+"brand:" + brand);
}
}
NotePad 子类
package com.hspedu.extend_.exercise;
public class NotePad extends Computer {
// 编写 NotePad 子类,继承 Computer 类,添加特有属性【color】
private String color;
public NotePad(String cpu,int memory,int disk,String color){
super(cpu, memory, disk);
this.color = color;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public void printInfo(){
System.out.println("NotePad信息如下:");
System.out.println(getDetails()+"color:" + color);
}
}
Test类
package com.hspedu.extend_.exercise;
public class ExtendsExercise03 {
public static void main(String[] args) {
// 编写 Test 类,在 main 方法中创建 PC 和 NotePad 对象,
// 分别给对象中特有的属性赋值,以及从 Computer 类继承的 属性赋值,
// 并使用方法并打印输出信息
PC pc = new PC("intel", 16, 500, "IBM");
pc.printInfo();
NotePad ipad = new NotePad("AMD", 8, 128, "LightGrey");
ipad.printInfo();
}
}
super关键字
super代表父类的引用,用于访问父类的属性、方法、构造器。
基本语法:
1) 访问父类的属性/方法,但不能访问父类的private属性/方法。 super.属性名/方法名;
2)访问父类的构造器;super(参数列表); 只能放在构造器的第一句,只能出现一句!
super带来的编程便利:
super和this的比较:
方法重写/覆盖(override)
基本介绍:方法覆盖(重写)就是子类有一个方法和父类的某个方法的名称、参数一样、返回类型一样(或者子类返回类型为父类的子类),此时我们就说子类的这个方法覆盖了父类的方法。
方法重载vs方法重写:
练习题:
- 编写一个 Person 类,包括属性/private(name、age),构造器、方法 say(返回自我介绍的字符串)。
- 编写一个 Student 类,继承 Person 类,增加 id、score 属性/private,以及构造器,定义 say 方法(返回自我介绍的信息)。
- 在 main 中,分别创建 Person 和 Student 对象,调用 say 方法输出自我介绍 代码
Person类
package com.hspedu.override_;
public class Person {
// 1) 编写一个 Person 类,包括属性/private(name、age),构造器、方法 say(返回自我介绍的字符串)
private String name;
private int age;
public Person(String name,int age){
this.name=name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String say(){
return "name:"+name+" age:"+age;
}
}
Student类
package com.hspedu.override_;
import java.util.jar.Attributes.Name;
public class Student extends Person{
// 2) 编写一个 Student 类,继承 Person 类,增加 id、score 属性/private,以及构造器,
// 定义 say 方法(返回自我介绍的信息)。
private int id;
private double score;
public Student(String name,int age,int id,double score){
super(name, age);
this.id = id;
this.score = score;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public String say(){
return "学生信息 "+super.say()+" id="+getId()+" score="+getScore();
}
}
main
package com.hspedu.override_;
public class Exercise {
public static void main(String[] args){
Student stu = new Student("Zoe", 22, 118, 98.0);
System.out.println(stu.say());
}
}
面向对象编程-多态
多(多种)态(状态)的基本介绍:方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
多态的具体体现
- 方法的多态
重载和重写就体现了多态 - 对象的多态
快速入门案例:
使用多态的机制来解决主人喂食物的问题
package com.hspedu.objectpoly;
public class Master {
private String name;
public Master(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//使用多态机制,可以统一的管理主人喂食的问题
//animal 编译类型是 Animal,可以指向(接收) Animal 子类的对象
//food 编译类型是 Food ,可以指向(接收) Food 子类的对象
public void feed(Animal animal, Food food) {
System.out.println("主人 " + name + " 给 " + animal.getName() + " 吃 " + food.getName());
}
//主人给小狗 喂食 骨
// public void feed(Dog dog, Bone bone) {
// System.out.println("主人 " + name + " 给 " + dog.getName() + " 吃 " + bone.getName());
// }
// //主人给 小猫喂 黄花鱼
// public void feed(Cat cat, Fish fish) {
// System.out.println("主人 " + name + " 给 " + cat.getName() + " 吃 " + fish.getName());
// }
//如果动物很多,食物很多
//===> feed 方法很多,不利于管理和维护
//Pig --> Rice
//Tiger ---> meat ... //... }
}
多态的注意事项
多态的前提是:两个对象(类)存在继承关系
多态的向上转型:
本质:父类的引用指向子类的对象。
语法:父类类型 引用名 = new 子类类型(); 例如Animal cat = new Cat();
特点:1)可以调用父类中所有成员(遵守访问权限),2)但不能调用子类特有的方法。(只有儿子继承父亲,没有父亲继承儿子的)(原因:编译阶段能调用哪些成员,是由编译类型来决定的。)
3)最终运行效果看子类(运行类型)的具体实现,即调用方法时,按照从子类(运行类型)开始查找方法。(调用重写的方法)
多态的向下转型:
语法:子类类型 引用名 = (子类类型) 父类引用; 例如 Cat cat = (Cat) animal
1)只能强转父类的引用,不能强转父类的对象;2)要求父类的引用必须指向的是当前目标类型的对象;3)当向下转型后,可以调用子类类型中所有的成员。
- 属性没有重写之说!属性的值看编译类型
package com.hspedu.objectpoly;
public class PloyDetail02 {
public static void main(String[] args) {
Base base = new Sub(); // 向上转型
System.out.println(base.count); // 看编译类型 10
Sub sub = new Sub();
System.out.println(sub.count); //? 20
}
}
class Base{ //父类
int count = 10; //属性
}
class Sub extends Base{ //子类
int count = 20; //属性
}
- instanceOf 比较操作符,用于判断对象的运行类型是否为 XX 类型或 XX 类型的子类型
package com.hspedu.objectpoly;
public class PolyDetail03 {
public static void main(String[] args){
BB bb = new BB();
System.out.println(bb instanceof BB); // true
System.out.println(bb instanceof AA);// true
//aa 编译类型 AA, 运行类型是 BB
//BB 是 AA 子类
AA aa = new BB();
System.out.println(aa instanceof AA);//true
System.out.println(aa instanceof BB);//true
Object obj = new Object();
System.out.println(obj instanceof AA);//false
String str = "hello";
//System.out.println(str instanceof AA);
System.out.println(str instanceof Object);//true
}
}
class AA{ // 父类
}
class BB extends AA{
}
练习题:
解读:
- s.count 是当前对象就是Sub的初始化count=20;
- s.display 是当前对象,如上输出20
- b == s,多态的向上转型,父类引用b指向子类对象s
- b.count,属性不会重写之说,看编译类型为Base,count=10
- b.display()为Sub的方法,多态的向上转型,子类Sub有display方法重写,输出Sub子类的结果count=20
Java的动态绑定机制:
- 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
- 当调用对象属性时,没有动态绑定机制,哪里声明哪里使用
<很复杂,建议看看视频。简要总结:调用方法看运行类型,调用属性看编译类型。>
多态的应用
1)多态数组
数组的定义类型为父类类型,里面保存的实际元素是子类类型。
例如:
Person[] persons = new Person[5];
persons[0] = new Student(“zoe”,19);
package com.hspedu.poly_.polyarr_;
public class PloyArray {
public static void main(String[] args){
Person[] persons = new Person[5];
persons[0] = new Person("zoe", 19);
persons[1] = new Student("jack",22,66);
persons[2] = new Student("song",20,96);
persons[3] = new Teacher("jay", 33,5000);
persons[4] = new Teacher("JJ", 30,10000);
//循环遍历多态数组,调用say
for(int i=0;i<persons.length;i++){
System.out.println(persons[i].say()); //动态绑定机制
// 调用子类特有的方法
if(persons[i] instanceof Student){ //判断person[i]的运行类型是不是Student
Student student = (Student) persons[i]; //向下转型
student.study();
}else if(persons[i] instanceof Teacher){
Teacher teacher = (Teacher) persons[i];
teacher.teach();
}else if(persons[i] instanceof Person){
//
}else{
System.out.println("类型有误,请检查");
}
}
}
}
父类
子类
2)多态参数
方法定义的形参类型为父类类型,实参类型允许为子类类型。
package com.hspedu.poly_.param_;
public class PloyPrameter {
public static void main(String[] args) {
Woker e = new Woker("Jack", 1000);
Manager m = new Manager("zoe", 10000, 2000);
Test t = new Test();
t.showEmpAnnual(e);
t.showEmpAnnual(m);
t.testWork(e);
t.testWork(m);
}
}
class Test{
public void showEmpAnnual(Employee e){ //父类形参允许实参是子类类型
System.out.println(e.getName()+" 年入 "+e.getAnnual());
}
public void testWork(Employee e){
if(e instanceof Woker){
Woker w = (Woker) e; //向下转型
w.work();
}else if(e instanceof Manager){
Manager m = (Manager) e;
m.manage();
}else{
System.out.println("类型有误,请重新输入!");
}
}
}
父类Employee和子类Work Manager
package com.hspedu.poly_.param_;
public class Employee {
private String name;
private double salary;
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
// 计算年工资getAnnual
public double getAnnual(){
return 12*salary;
}
}
package com.hspedu.poly_.param_;
public class Woker extends Employee{
public Woker(String name,double salary){
super(name, salary);
}
@Override
public double getAnnual(){
return super.getAnnual();
}
public void work(){
System.out.println("员工"+getName()+"正在工作");
}
}
package com.hspedu.poly_.param_;
public class Manager extends Employee{
private double bonus;
public Manager(String name,double salary,double bonus){
super(name, salary);
this.bonus = bonus;
}
public void manage(){
System.out.println("经理"+getName()+"管理中。。");
}
@Override
public double getAnnual(){
return super.getAnnual()+bonus;
}
}
Object类详解
== 和equals的对比
== 是一个比较运算符,既可以判断基本类型,也可以判断引用类型。
-
如果判断基本类型就是判断值是否相等;
-
如果判断引用类型就是判断地址是否相等(即是不是同一个对象)。
equals 是Obeject类中的方法,只能判断引用类型。
默认判断你的地址是否相等(判断对象是否相同)。
子类中往往重写该方法,用于判断内容是否相等。如下面为String中equals的源码,判断String内容是否一致:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
return (anObject instanceof String aString)
&& (!COMPACT_STRINGS || this.coder == aString.coder)
&& StringLatin1.equals(value, aString.value);
}
Integer也重写了 Object 的 equals 方法, //变成了判断两个值是否相等
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
如何重写equals方法
判断两个 Person 对象的内容是否相等,如果两个 Person 对象的各个属性值都一样,则返回 true,反之 false
package com.hspedu.obejct_;
public class EqualsExercise01 {
public static void main(String[] args) {
Person person1 = new Person("jack", 20, '男');
Person person2 = new Person("jack", 2, '男');
System.out.println(person1.equals(person2));
}
}
class Person{
private String name;
private int age;
private char gender;
public Person(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
public boolean equals(Object obj){
if(this == obj){
return true;
}
if(obj instanceof Person){
Person p = (Person)obj;
return this.name.equals(p.name) && this.age==p.age && this.gender==p.gender;
}
return false;
}
}
hashCode方法
返回该对象的哈希码值,支持此方法是为了提高哈希表的性能。
1)提高具有哈希结构的容器的效率!
2)两个引用,如果指向的是同一个对象,则哈希值肯定是一样的
3)两个引用,如果指向的是不同对象,则哈希值是不一样的
4)哈希值主要根据地址号来的,不能完全将哈希值等价于地址!
package com.hspedu.obejct_;
public class HashCode {
public static void main(String[] args) {
AA aa = new AA();
AA b = aa;
AA aa2 = new AA();
System.out.println(aa.hashCode()); //925858445
System.out.println(b.hashCode()); // 925858445
System.out.println(aa2.hashCode()); //798154996
}
}
class AA{}
- 后面在集合中hashCode如果需要的话也会重写,如何重写请看集合的课程
toString方法
基本介绍:默认返回 全类名+@+哈希值的十六进制,【查看 Object 的 toString 方法】 子类往往重写 toString 方法,用于返回对象的属性信息。
1)重写 toString 方法,打印对象或拼接对象时,都会自动调用该对象的 toString形式
2)当直接输出一个对象时,toString方法会被默认的调用,比如 System.out.println(monster); 就会默认调用 monster.toString()
finalize方法
1)当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作。(资源包括属性、数据库连接、文件等等)
2)什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用 finalize 方法。
3)垃圾回收机制的调用,是由系统来决定(即有自己的 GC 算法), 也可以通过 System.gc() 主动触发垃圾回收机制。
在实际开发中,几乎不会运用 finalize , 为了面试得了解。
断点调试(debug)
断点调试过程中是运行状态,是以对象的运行类型来执行的。
项目-零钱通
这个项目我没做,有时间/感兴趣的话再看
本章作业
P342-358是作业的讲解,没写出来可以看看。
1
package com.hspedu.homework;
public class Homework01 {
public static void main(String[] args) {
Person[] persons = new Person[5];
persons[0] = new Person("jack", 20, "大数据工程师");
persons[1] = new Person("jack", 22, "大数据工程师");
persons[2] = new Person("jack", 10, "大数据工程师");
persons[3] = new Person("jack", 23, "大数据工程师");
persons[4] = new Person("jack", 5, "大数据工程师");
Person temp = null;
for(int i =0;i<persons.length-1;i++){
for(int j =0;j<persons.length-1-i;j++){
if(persons[j].getAge()<persons[j+1].getAge()){
temp = persons[j];
persons[j] = persons[j+1];
persons[j+1] = temp;
}
}
}
// 输出排序的结果
for(int i =0;i<persons.length;i++){
System.out.println(persons[i]);
}
}
}
class Person{
private String name;
private int age;
private String job;
public Person(){
}
public Person(String name, int age, String job) {
this.name = name;
this.age = age;
this.job = job;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
@Override
public String toString(){
return "Person{"+
"name="+name+'\''+
",age="+age+
",job="+job+'\''+
'}';
}
}
14
解释:C的无参构造器执行的this 需要有参构造器,执行C的有参构造器,执行super找到父类B的有参构造器,输出”hahah我是B类的有参构造“,构造器默认有super(),需要到B的父类A中输出”我是A类“,随后继续执行B。。。
待续。。。
3. 面向对象编程(高级部分)(373-423)
类变量和类方法
类变量定义
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量。
任何一个该类的对象去访问它时,取到的都是相同的值。同样,任何一个该类的对象去修改它时,修改的也是同一个变量。
类变量内存布局
静态变量放在哪里? 有些书中说在方法区,有些说在堆中。静态变量的存放和JDK版本相关,jdk8以上的都存在class堆中。
不管static变量在哪里,共识1)static变量是同一类所有对象共享;2)static类变量在类加载的时候就生成了(即使没有创建对象实例,也可以访问)。
静态变量被对象共享,不影响对静态变量的使用。
如何定义类变量
访问修饰符 static 数据类型 变量名;[推荐]
static 访问修饰符 数据类型 变量名;
如何访问类变量
静态变量的访问修饰符的访问权限范围和普通属性是一样的
类名.类变量名 【推荐】
或者
对象名.类变量名
类变量使用注意事项和细节讨论
类方法
类方法的使用场景
类方法使用注意事项
理解main方法语法
- main方法是虚拟机调用,java虚拟机调用类的main()方法,必须得是public权限。
- main方法不必创建对象,所以必须是static
- String[] args参数在命令台执行java程序的最末尾传入。(类似python运行深度学习程序传参)
特别提示:
- 在 main()方法中,可以直接调用 main 方法所在类的静态方法或静态属性。
- main()方法 不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后, 才能通过这个对象去访问类中的非静态成员。
代码块
代码块又称初始化块,属于类中的成员[即 类中的一部分],类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。
但和方法不同的是:没有方法名,没有返回值,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。
基本语法:
[修饰符]{
代码
};
1)修饰符可选,要写也只能写static
2)代码块分为两类,使用static修饰的叫静态代码块,没有的叫做普通代码块/非静态代码块
3)逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
4);(分号)可以写上,也可以省略
更好的理解:
1)代码块相当于另一种形式的构造器(对构造器的补充机制),可以做初始化的操作
2)场景:如果多个构造器都有重复的语句,可以抽取到初始化块中,提高代码的重用性
代码块使用注意事项和细节
1)static代码块的作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象就执行一次。
2)类什么时候被加载?(重要)
- 创建对象实例时(new)
- 创建子类对象实例,父类也会被加载
- 使用类的静态成员时(静态属性,静态方法)
3)普通代码块,在创建对象实例时,会被隐式的调用(创建一次调用一次)。如果只是使用类的静态成员时,普通代码块不会被执行。
调用顺序:父类和子类的静态代码块和静态属性在类中共享,所以优先。其次是父类的普通代码块和普通属性初始化,随后是父类的构造方法,最后执行子类的。
练习题:
单例设计模式
设计模式:在大量事件中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考能力。
单例(单个的实例):
- 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能 存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
- 单例模式有两种方式:1)饿汉式;2)懒汉式。
应用场景:
单例模式步骤[饿汉式]:
- 构造器私有化
- 类的内部直接创建对象(static)
- 提供一个公共的静态方法,返回gf对象
懒汉式,只有当用户使用getInstance时,才返回对象,后面再次调用时会返回上次创建的对象。
单例模式应用案例:
package com.hspedu.single_;
public class SingleTon02 {
public static void main(String[] args){
// GirlFriend xh = new GirlFriend("小红");
// GirlFriend xx = new GirlFriend("小黄");
//通过方法获取对象
GirlFriend instance = GirlFriend.getInstance();
System.out.println(instance);
GirlFriend instance2 = GirlFriend.getInstance();
System.out.println(instance2);
System.out.println(instance==instance2); //相同的对象
}
}
// 希望在程序运行过程中,只能创建一个GirlFriend对象
class GirlFriend{
private String name;
/* 饿汉式
// 为了能够在静态方法中,返回GirlFriend对象,需要将其修饰成static
private static GirlFriend gf = new GirlFriend("小红红");
// 如何保障只能创建一个GirlFriend对象?
// 步骤[单例模型-饿汉式]:
// 1. 构造器私有化
// 2. 类的内部直接创建对象(static)
// 3. 提供一个公共的静态方法,返回gf对象
private GirlFriend(String name){
this.name=name;
System.out.println("构造器被调用");
}
public static GirlFriend getInstance(){
return gf;
}
*/
// 懒汉式
private static GirlFriend gf; //默认null
private GirlFriend(String name){
this.name=name;
System.out.println("构造器被调用");
}
public static GirlFriend getInstance(){
if(gf == null){ //需要使用的时候再创建
gf = new GirlFriend("小明");
}
return gf;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
final关键字
final可以修饰类、属性、方法和局部变量
应用场景
1)当不希望类被继承时,可以用final修饰【final class 类名】
2)当不希望父类的某个方法被在子类覆盖/重写(override)时,可以用final关键字修饰 【访问修饰符 final 返回类型 方法名】
3)当不希望类的某个属性的值被修改,可以用final修饰【public final double TAX_RATE = 0.08;】
4)当不希望某个局部变量被修改,可以用final修饰【final double NUM = 0.0】
final使用注意事项和细节
1)final修饰的属性又叫常量,一般用 XX_XX_XXl来命名,比如TAX_RATE
2)final修饰的属性正在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一:
- 定义时:如public final double TEX_RATE=0.08;
- 构造器中
- 代码块中
3)如果final修饰的属性是静态的,则初始化的位置只能是:
- 定义时
- 静态代码块中
4)如果final类不能继承,但是可以实例化对象。
5)如果类不是final类,但是含有final方法,则该方法虽然不能重写,但可以被继承。
6)如果一个类已经是final类,就没必要再将方法修饰成final方法。
7)final不能修饰构造方法
8)final和static往往搭配使用,效率更高,不会导致类加载(底层编译器做了优化处理)
9)包装类(Integer, Double, Float, Boolean等都是final),String也是final类
课堂练习:
抽象类
当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法,这个方法就是抽象方法,用abstract了来修饰该类就是抽象类。
抽象类介绍
抽象类使用的注意事项和细节
课堂练习:
抽象类最佳实践-模板设计模式
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但是子类总体上会保留抽象类的行为方式。
模板设计模式能解决的问题:
1)当功能内部一部分实现是确定的,一部分实现是不确定的。可以把不确定的部分暴露出去,让子类实现。
2)编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给子类实现,就是一种模板模式。
最佳实践:统计不同任务的完成时间
接口
基本介绍
注意事项和细节
实现接口 vs 继承类
实现接口是对 java 单继承机制的一种补充
当子类继承了父类,就自动的拥有父类的功能。如果子类需要扩展功能,可以通过实现接口的方式扩展。
接口和继承解决的问题不同:
继承的价值主要在于:解决代码的复用性和可维护性
接口的价值主要在于:设计,设计好各种规范(方法),让其他类去实现这些方法。即更加的灵活…
继承满足 is - a的关系,而接口只需满足 like - a的关系
接口在一定程度上实现代码解耦 [即:接口规范性+动态绑定机制]
接口的多态特性
1)多态参数
2)多态数组
3)接口存在多态传递现象
课堂练习:
解决方法:明确指定的x,访问接口的 x 就使用A.x;访问父类的 x 就使用super.x
内部类
定义在外部类局部位置(方法中/代码块) :
(1) 局部内部类(有类名)
(2) 匿名内部类 (没有类名)
定义在外部类的成员位置:(本质上为一个成员)
(1) 成员内部类(没用static修饰)
(2) 静态内部类(使用static修饰)
基本介绍
类的五大成员:属性、构造器、方法、代码块、内部类
基本语法
class Outer{ // 外部类
class Inner{ // 内部类
}
}
class Other{ // 外部其他类
}
局部内部类的使用
局部内部类是定义在外部类的局部位置,比如方法中,并且有类名
- 可以直接访问外部类的所有成员,包含私有的
- 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量不能使用修饰符,但是可以使用final修饰(局部变量也可以使用)。
- 作用域:仅仅在定义它的方法或代码块中
- 局部内部类—访问—>外部类的成员 [访问方式:直接访问]
- 外部类—访问—>局部内部类的成员 [访问方式:创建对象,再访问](必须在作用域内)
- 外部其他类—不能访问—>局部内部类
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则。如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。(外部类.this 本质就是外部类的对象,即哪个对象调用了该方法,外部类.this就指向该对象。)
System.out.println(“外部类的n2=”+外部类名.this.n2);
重要tips:
- 局部内部类定义在方法/代码块中
- 作用域在方法体或者代码块中
- 本质仍然是一个类
匿名内部类的使用
- 本质是类
- 内部类
- 该类没有名字
- 还是一个对象
匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名。
1)基本语法
new 类或接口(参数列表){
类体x
};
- jdk底层在创建匿名内部类Outer04$1,立马就创建了Outer04$1实例,并且把地址返回给tiger。
- 匿名内部类(Outer04$1)只使用一次,但是对象(tiger)可以重复使用。
2)匿名内部类即是一个类的定义,本身也是一个对象。因此,它既有定义类的特征,也有创建对象的特征。
3)可以直接访问外部类的所有成员,包含私有的
4)不能添加访问修饰符,因为它的地位就是一个局部变量
5)作用域:仅仅在定义它的方法或代码块中
6)匿名内部类—访问—>外部类成员 [访问方式:直接访问]
7)外部其他类—不能访问—>匿名内部类
8)如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则。如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问。
成员内部类的使用
成员内部类是定义在外部类的成员位置,并且没有static修饰。
-
可以直接访问外部类的所有成员,包含私有的
-
可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
-
作用域和外部类的其他成员一样,为整个类体。先创建成员内部类对象,再调用方法。
-
成员内部类—访问—>外部类成员(比如属性)[访问方式:直接访问]
-
外部类—访问—>成员内部类[访问方法:创建对象,再访问]
-
外部其他类—访问—>成员内部类
-
如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则。如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
静态内部类的使用
静态内部类是定义在外部类的成员位置,并且有static修饰
-
可以直接访问外部类的所有静态成员,包含私有,但不能直接访问非静态成员
-
可以添加任意访问修饰符(public、protected 、默认、private)。因为它的地位就是一个成员
-
作用域:同其他的成员,为整个类体
-
静态内部类—访问—>外部类(比如静态属性) [访问方式:直接访问是所有静态成员]
-
外部类—访问呢—>静态内部类 [访问方式:创建对象,再访问]
-
外部其他类—访问—>静态内部类
-
如果外部类和静态内部类的成员重名时,静态内部类访问的话,默认遵循就近原则。如果访问外部类的成员,则可以用(外部类名.成员)去访问。(成员是静态的,不用加this)
练习题
验证代码:
package com.hspedu.Innerclass_;
public class Exercise {
public Exercise(){
Inner s1 = new Inner();
s1.a=10;
System.out.println("s1.a="+s1.a);
Inner s2 = new Inner();
System.out.println("s2.a="+s2.a);
}
class Inner{
public int a = 5;
}
public static void main(String[] args) {
Exercise e = new Exercise();
Inner r = e.new Inner();
System.out.println("r.a="+r.a);
}
}
面向对象编程部分的学习完成,下篇为枚举和注解/异常/常用类/集合/泛型/多线程/IO流等内容:【Java基础】枚举和注解/异常/常用类/集合/泛型/多线程/IO流(韩顺平P424-660)