面向对象 类与对象 类与对象引出
单独的定义变量解决
使用数组解决
不利于数据的管理
效率低
java设计者引入类与对象(OQP),根本原因就是现有的技术,不能完美的解决新的新的需求.
类与对象概述 类和对象的区别和联系
通过上面的案例和讲解我们可以看出;
类是抽象的,概念的,代表一类事物,比如人类,猫类…即它是数据类型.
对象是具体的,实际的,代表一个具体事物,即是实例.
类是对象的模板,对象是类的一个个体,对应一个实例
属性/成员变量
从概念或叫法上看:成员变量=属性= field(即成员变量是用来表示属性的,授课中,统一叫属性)
属性是类的一个组成部分,一般是基本数据类型,也可是引用类型(对象,数组)。 比如我们前面定义猫类的int age就是属性
属性的定义语法同变量,示例:访问修饰符属性类型属性名;这里老师简单的介绍访问修饰符:控制属性的访问范围 有四种访问修饰符public, proctected,默认, private
属性的定义类型可以为任意类型,包含基本类型或引用类型
属性如果不赋值,有默认值,规则和数组一致。具体说: int 0,short 0.byte , long 0, float 0.0,double 0.0,char \u0000,boolean false,String null
Java内存的结构分析
栈:一般存放基本数据类型(局部变量)
堆:存放对象(Cat cat,数组等)
方法区:常量池(常量,比如字符串),类加载信息
Java创建对象的流程简单分析 Person p = new Person(); p.name = “jack” ; p.age = 10
先加载Person类信息(属性和方法信息,只会加载一次)
在堆中分配空间,进行默认初始化(看规则)
把地址赋给p,p就指向对象
进行指定初始化,比如p.name =” jack “p.age = 10
成员方法 基本介绍 在某些情况下,我们要需要定义成员方法(简称方法)。比如人类:除了有一些属性外(年龄,姓名..),我们人类还有一些行为比如:可以说话、跑步..,通过学习,还可以做算术题。这时就要用成员方法才能完成。现在要求对Person类完善。
成员方法快速入门
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Method01 {public static void main (string[] args) {Person p1 = new Person ();p1.speak(); } } class Person {String name; int age;public void speak () {System.out.println("我是一个好人" ); } }
方法调用机制
成员方法的定义 public 返回数据类型 方法名 (形参列表..){ //方法体语句; return返回值; }
参数列表:表示成员方法输入cal(int n)
数据类型(返回类型):表示成员方法输出, void表示没有返回值
方法主体:表示为了实现某一功能代码块
return语句不是必须的。
总结一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1. 什么是方法? 答:方法是一种语法结构,它可以把一段代码封装成一个功能,以便重复调用 2. 方法的完整格式是什么样的? 修饰符 返回值类型 方法名( 形参列表 ){ 方法体代码(需要执行的功能代码) return 返回值; } 3. 方法要执行必须怎么办? 必须调用才执行; 方法名(...); 4. 使用方法有什么好处? 答:提高代码的复用性,提高开发效率,使程序逻辑更清晰。
注意事项和使用细节
一个方法最多有一个返回值
返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
如果方法要求有返回数据类型,则方法体中最后的执行语句必须为return值;而且要求返回值类型必须和return的值类型一致或兼容
如果方法是void,则方法体中可以没有return语句,或者只写 return ;
一个方法可以有0个参数,也可以有多个参数,中间用逗号隔开,比如getSum(int n1,int n2)
参数类型可以为任意类型,包含基本类型或引用类型,比如 printArr(int[][] map)
调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数!
方法定义时的参数称为形式参数,简称形参;方法调用时的传入参数称为实际参数,简称实参,实参和形参的类型要一致或兼容、个数、顺序必须一致!
方法体 里面写完成功能的具体的语句,可以为输入、输出、变量、运算、分支、循环、方法调用,但里面不能再定义方法!即:方法不能嵌套定义。
成员方法与传参机制 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static void main (String[] args) i{ int a = 10 ; int b = 20 ; AA obj = new AA (); obj.swap(a, b); System.out.println( "a=" + a + " b=" + b); } class AA { public void swap (int a,int b) { System.out.println("\na和b交换前的值\na=" + a + "\tb=" + b); int tmp = a; a = b; b = tmp; System.out.println("\na和b交换后的值\na=" + a + "\tb=" + b); } }
先记住一个结论:Java的参数传递机制都是:值传递,传递的是实参存储的值的副本。
总结一下:
1 2 3 4 1. 基本类型和引用类型的参数在传递的时候有什么不同? = 都是值传递 - 基本类型的参数传递存储的数据值。 - 引用类型的参数传递存储的地址值。
方法的执行原理 我们知道Java程序的运行,都是在内存中执行的,而内存区域又分为栈、堆和方法区。那Java的方法是在哪个内存区域中执行呢?
答案是栈内存。 每次调用方法,方法都会进栈执行;执行完后,又会弹栈出去。
方法进栈和弹栈的过程,就类似于手枪子弹夹,上子弹和击发子弹的过程。最后上的一颗子弹是,第一个打出来的;第一颗上的子弹,是最后一个打出来的。
假设在main方法中依次调用A方法、B方法、C方法,在内存中的执行流程如下:
每次调用方法,方法都会从栈顶压栈执行没执行
每个方法执行完后,会从栈顶弹栈出去
方法使用常见的问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - 1. 方法在内种没有先后顺序,但是不能把一个方法定义在另一个方法中。 - 2. 方法的返回值类型写void (无返回申明)时,方法内不能使用return 返回数据, 如果方法的返回值类型写了具体类型,方法内部则必须使用return 返回对应类型的数据。 - 3. return 语句的下面,不能编写代码,属于无效的代码,执行不到这儿。 - 4. 方法不调用就不会执行, 调用方法时,传给方法的数据,必须严格匹配方法的参数情况。 - 5. 调用有返回值的方法,有3 种方式: ① 可以定义变量接收结果 ② 或者直接输出调用, ③ 甚至直接调用; - 6. 调用无返回值的方法,只有1 种方式: 只能直接调用。
方法参数传递案例 方法参数传递案例1 1 2 3 4 5 6 7 8 9 10 11 12 需求:输出一个int 类型的数组内容,要求输出格式为:[11 , 22 , 33 , 44 , 55 ]。 分析: 1. 方法是否需要接收数据进行处理? 方法要打印int 类型数组中的元素,打印哪一个数组需求并不明确; 所以可以把int 数组写成参数,让调用者指定 2. 方法是否需要返回数据? 方法最终的目的知识打印数组中的元素。 不需要给调用者返回什么,所以不需要返回值,返回值类型写void 3. 方法内部的业务:遍历数组,并输出相应的内容
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class MethodTest3 { public static void main (String[] args) { int [] arr = {10 , 30 , 50 , 70 }; printArray(arr); int [] arr2 = null ; printArray(arr2); int [] arr3 = {}; printArray(arr3); } public static void printArray (int [] arr) { if (arr == null ){ System.out.println(arr); return ; } System.out.print("[" ); for (int i = 0 ; i < arr.length; i++) { if (i == arr.length - 1 ){ System.out.print(arr[i]); }else { System.out.print(arr[i] + ", " ); } } System.out.println("]" ); } }
方法参数传递案例2 1 2 3 4 5 6 7 8 9 10 11 12 需求:比较两个int 类型的数组是否一样,返回true 或者false 分析: 1. 方法是否需要接收数据进行处理? 因为,方法中需要两个int 数组比较,但是需求并不明确是哪两个数组; 所以,需要接收两个int 类型的数组,形参声明为:int [] arr1,int [] arr2 2. 方法是否需要返回数据? 因为,方法最终的结果需要true 或者false ; 所以,返回值类型是boolean 3. 方法内部的业务:判断两个数组内容是否一样。
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class MethodTest4 { public static void main (String[] args) { int [] arr1 = {10 , 20 , 30 }; int [] arr2 = {10 , 20 , 30 }; System.out.println(equals(arr1, arr2)); } public static boolean equals (int [] arr1, int [] arr2) { if (arr1 == null && arr2 == null ){ return true ; } if (arr1 == null || arr2 == null ) { return false ; } if (arr1.length != arr2.length){ return false ; } for (int i = 0 ; i < arr1.length; i++) { if (arr1[i] != arr2[i]){ return false ; } } return true ; } }
方法递归 递归能解决什么问题?
各种数学问题如:8皇后问题,汉诺塔,阶乘问题,迷宫问题,球和篮子的问题
各种算法中也会使用到递归,比如快排,归并排序,二分查找,分治算法等
将用栈解决的问题–>递归代码比较简洁
递归执行机制 递归举例 列举两个小案例,来帮助大家理解递归调用机制
打印问题
阶乘问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public void test (int n) {if (n > 2 ){ test(n - 1 ); } System.out.println("n=" + n); } public int factorial (int n) {if (n == 1 ) {return 1 ;}else { return factorial(n - 1 ) * n;} }
递归重要规则
执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
方法的局部变量是独立的,不会相互影响,比如n变量
如果方法中使用的是引用类型变量(比如数组,对象),就会共享该引用类型的数据.
递归必须向退出递归的条件逼近,否则就是无限递归,
当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就 将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕。
重载
一个类中,出现多个方法的名称相同,但是它们的形参列表是不同的,那么这些方法就称为方法重载了。
减轻了起名的麻烦
减轻了记名的麻烦
注意事项和使用细节
方法名∶必须相同
形参列表:必须不同(形参类型或个数或顺序,至少有一样不同,参数名无要求)
返回类型:无要求
课堂练习题
判断题: 与void show(int a,char b,double c)构成重载的有:[b c d e ] a) void show(int x,char y,double z) //不是 b) int show(int a,double c,char b) //是 c) void show(int a,double c.char b){} //是 d) boolean show(int c.char b) //是 e) void show(double c) //是 f) double show(int x,char y,double z) //不是 g) void shows(){} //不是
可变参数 基本概念 java允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。就可以通过可变参数实现
基本语法 访问修饰符返回类型方法名(数据类型…形参名){}
注意事项和使用细节
可变参数的实参可以为0个或任意多个。
可变参数的实参可以为数组。
可变参数的本质就是数组.
可变参数可以和普通类型的参数一起放在形参列表,但必须保业可艾乡多以取口
一个形参列表中只能出现一个可变参数 public int sum(String str,int… a, String… s)l/
Java编程案例
编程思维、编程能力不是一朝一夕形成的,需要大量思考,练习和时间的沉淀。
具体措施:前期,建议先模仿;后期,自然就能创新了;
案例一:买飞机票 1 2 3 4 5 6 7 8 9 10 1. 首先,考虑方法是否需要接收数据处理? 阅读需求我们会发现,不同月份、不同原价、不同舱位类型优惠方案都不一样; 所以,可以将原价、月份、舱位类型写成参数 2. 接着,考虑方法是否有返回值? 阅读需求我们发现,最终结果是求当前用户的优惠票价 所以,可以将优惠票价作为方法的返回值。 3. 最后,再考虑方法内部的业务逻辑 先使用if 判断月份是旺季还是淡季,然后使用switch 分支判断是头等舱还是经济舱,计算 票价
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class Test1 { public static void main (String[] args) { double price = calculate(1000 , 11 , "头等舱" ); System.out.println("优惠价是:" + price); } public static double calculate (double price,int month,String type) { if (month >= 5 && month <= 10 ) { switch (type){ case "头等舱" : price *= 0.9 ; break ; case "经济舱" : price *= 0.85 ; break ; } }else { switch (type){ case "头等舱" : price *= 0.7 ; break ; case "经济舱" : price *= 0.65 ; break ; } } return price; } }
案例二:开发验证码 分析一下,需求是要我们开发一个程序,生成指定位数的验证码。考虑到实际工作中生成验证码的功能很多地方都会用到,为了提高代码的复用性,我们还是把生成验证码的功能写成方法比较好。
1 2 3 4 5 6 7 8 9 10 11 12 1. 首先,考虑方法是否需要接收数据处理? 要求生成指定位数的验证码,到底多少位呢?让调用者传递即可 所以,需要一个参数,用来表示验证码的位数 2. 接着,考虑方法是否需要有返回值? 该方法的结果,就是为了得到验证码 所以,返回值就是验证码; 3. 最后,再考虑方法内部的业务逻辑 1 )先按照方法接收的验证码位数n,循环n次 2 )每次循环,产生一个字符,可以是数字字符、或者大小写字母字符 3 )定义一个String类型的变量用于记住产生的每位随机字符
按照思路,编写代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class Test2 { public static void main (String[] args) { System.out.println(createCode(8 )); } public static String createCode (int n) { Random r = new Random (); String code = "" ; for (int i = 1 ; i <= n; i++) { int type = r.nextInt(3 ); switch (type) { case 0 : code += r.nextInt(10 ); break ; case 1 : char ch1 = (char ) (r.nextInt(26 ) + 65 ); code += ch1; break ; case 2 : char ch2 = (char ) (r.nextInt(26 ) + 97 ); code += ch2; break ; } } return code; } }
案例三:评委打分 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1. 首先,考虑方法是否需要接收数据来处理? 需求中说,有多个评委的打分,但是到底多少个评委呢? 可以由调用者传递 所以,我们可以把评委的个数写成参数; 2. 接着,考虑方法是否需要有返回值? 需求中,想要的最终结果是平均分 所以,返回值就是平均分; 3. 最后,再考虑方法内部的业务逻辑 1 )假设评委的个位为n个,那么就需要n个评委的分数,首先可以新建一个长度为n的数组, 用来存储每一个评委的分数 2 )循环n次,使用Scanner键盘录入n个1 ~100 范围内的整数,并把整数存储到数组中 3 )求数组中元素的总和、最大值、最小值 4 )最后再计算平均值; 平均值 = (和-最大值-最小值)/(数组.length-2 );
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class Test3 { public static void main (String[] args) { System.out.println("当前选手得分是:" + getAverageScore(6 )); } public static double getAverageScore (int n) { int [] scores = new int [n]; Scanner sc = new Scanner (System.in); for (int i = 0 ; i < scores.length; i++) { System.out.println("请您录入第" + (i + 1 ) +"个评委的分数:" ); int score = sc.nextInt(); scores[i] = score; } int sum = 0 ; int max = scores[0 ]; int min = scores[0 ]; for (int i = 0 ; i < scores.length; i++) { int score = scores[i]; sum += score; if (score > max){ max = score; } if (score < min){ min = score; } } return 1.0 * (sum - min - max) / (number - 2 ); } }
案例四:数字加密 仔细阅读需求后发现,简答来说该需求要做的事情,就是把一个4位数的整数,经过一系列的加密运算(至于怎么运算,待会再详细分析),得到一个新的整数。
我们还是把这个需求用方法来实现,按照下面的思维模式进行分析
1 2 3 4 5 6 7 8 9 10 11 12 13 1. 首先,考虑方法是否需要接收数据处理? 需要一个4 位数,至于是哪一个数,让方法的调用者传递。 所以,方法的参数,就是这个需要加密的四位数 2. 接着,考虑方法是否需要有返回值? 方法最终的结果是一个加密后的数据 所以,返回值就表示为加密后的数据。 3. 最后,再考虑方法内部的业务逻辑,这里的业务逻辑就是那一系列的加密运算 1 )先要把4 位数整数拆分为,4 个数字,用一个数组保存起来 2 )再将数组中的每一个元素加5 ,再对10 取余 3 )最后将数组中的元素反转,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public class Test4 { public static void main (String[] args) { System.out.println("加密后的结果是:" + encrypt(8346 )); } public static String encrypt (int number) { int [] numbers = split(number); for (int i = 0 ; i < numbers.length; i++) { numbers[i] = (numbers[i] + 5 ) % 10 ; } reverse(numbers); String data = "" ; for (int i = 0 ; i < numbers.length; i++) { data += numbers[i]; } return data; } public static void reverse (int [] numbers) { for (int i = 0 , j = numbers.length - 1 ; i < j; i++,j--) { int temp = numbers[j]; numbers[j] = numbers[i]; numbers[i] = temp; } } public static int [] split(int number) { int [] numbers = new int [4 ]; numbers[0 ] = number / 1000 ; numbers[1 ] = (number / 100 ) % 10 ; numbers[2 ] = (number / 10 ) % 10 ; numbers[3 ] = number % 10 ; return numbers; } }
案例五:数组拷贝 仔细阅读需求发现,想要实现的效果就是:给定一个数组,然后经过我们编写的程序,得到一个和原数组一模一样的数组。
1 2 3 4 5 6 7 8 9 10 11 12 1. 首先,考虑方法是否需要接收数据处理? 该方法的目的是拷贝数组,拷贝哪一个数组呢? 需要调用者传递 所以,参数应该是一个数组 2. 接着,考虑方法是否需要有返回值? 该方法最终想要得到一个新数组 所以,返回值是拷贝得到的新数组 3. 最后,考虑方法内部的业务逻辑? 1 )创建一个新的数组,新数组的长度和元素数组一样 2 )遍历原数组,将原数组中的元素赋值给新数组 3 )最终将新数组返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class Test5 { public static void main (String[] args) { int [] arr = {11 , 22 , 33 }; int [] arr2 = copy(arr); printArray(arr2); arr2[1 ] = 666 ; System.out.println(arr[1 ]); } public static int [] copy(int [] arr){ int [] arr2 = new int [arr.length]; for (int i = 0 ; i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } public static void printArray (int [] arr) { System.out.print("[" ); for (int i = 0 ; i < arr.length; i++) { System.out.print(i==arr.length-1 ? arr[i] : arr[i] + ", " ); } System.out.println("]" ); } }
案例六:抢红包 我们还是把这个案例用一个方法来编写,同样按照下面的模式来分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 1. 首先,考虑方法是否需要接收数据处理? 需要接收5 个红包,至于是哪5 个红包,可以有调用者传递;把5 个红包的数值,用数组来存 储。 所以,参数就是一个数组 2. 接着,考虑方法是否需要有返回值? 按照需求的效果,抢完红包就直接打印了,不需要返回值 3. 最后,考虑方法内部的业务逻辑是怎么的? 思考:红包实际上是数组中的元素,抢红包实际上随机获取数组中的元素;而且一个红包只能抢一次,怎么做呢?我们可以把数组中获取到元素的位置,置为0 ,下次再或者这个位置的元素一判断为0 ,再重新获取新的元素,依次内推,直到把数组中所有的元素都获取完。 我们我们把抽红包的思路再整理一下: 1 )首先,写一个循环,循环次数为数组的长度 2 )每次循环,键盘录入,提示"用户录入任意键抽奖:" 3 )随机从数组中产生一个索引,获取索引位置的元素,这个元素就表示抽的红包 如果值不为0 ,则打印如:"恭喜您,您抽中了520元" ,把这个位置元素置为0 如果值为0 ,则说明这个红包被抽过,重新循环到第2 步,重新抽奖 【注意:如果当前这一次没有抽中,这一次抽奖机会被浪费掉了,我们可以把控 制循环的次数自减一下】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class Test6 { public static void main (String[] args) { int [] moneys = {100 ,999 ,50 ,520 ,1314 }; start(moneys); } public static void start (int [] moneys) { for (int i = 0 ; i < moneys.length; i++) { while (true ){ Scanner sc = new Scanner (System.in); System.out.print("用户录入任意键抽奖:" ); String msg = sc.next(); Random r = new Random (); int index = r.nextInt(moneys.length); int money = moneys[index]; if (money!=0 ){ System.out.println("恭喜您,您抽中了" +money+"元" ); moneys[index] = 0 ; break ; }else { i--; } } } } }
案例七:找素数 首先我们得统一认识一下什么是素数:只能被1和本身整除的数是素数 ,比如:3、7是素数,9,21不是素数(因为9可以被3整除,21可以被3和7整除)
再思考题目需求该怎么做?打印输出101~200之间的素数,并求有多少个? ,我们也是把这个需求写成一个方法,还是按照三个步骤分析方法如何编写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 1. 首先,考虑方法是否需要接收数据处理? 该方法是求一个范围内的素数,一个范围需要两个数据来确定,比如:101 ~200 所以,方法需要两个参数来接收范围的开始值start,和范围的结束值end 2. 接着,考虑方法是否需要返回值? 该方法需要求一个范围内的素数的个数 所以,返回值就是素数的个数 3. 最后,考虑方法内部的业务逻辑 思考:怎么判断一个数是素数呢?要仅仅抓住,素数的要求:“只能被1 和本身整除的数是素数”。我们可以从反向思考,如果这个数只要能被除了1 和本身以外的数整除,那么这个数就不是素数。 int num = 9 ; boolean flag = true ; for (int j=2 ; j<9 -1 ; j++){ if (num%j==0 ){ flag = false ; } } 把上面的代码循环执行,每次循环然后把num换成start~end之间的整数即可。
编写代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class Test7 { public static void main (String[] args) { System.out.println("当前素数的个数是:" + search(101 , 200 )); } public static int search (int start, int end) { int count = 0 ; for (int i = start; i <= end ; i++) { boolean flag = true ; for (int j = 2 ; j <= i / 2 ; j++) { if (i % j == 0 ){ flag = false ; break ; } } if (flag){ System.out.println(i); count++; } } return count; } }
面向对象基础 面向对象
总结一些:所谓编写对象编程,就是把要处理的数据交给对象,让对象来处理。
面向对象编程有什么好处? Java的祖师爷,詹姆斯高斯林认为,在这个世界中 万物皆对象! 任何一个对象都可以包含一些数据,数据属于哪个对象,就由哪个对象来处理。
所以面向对象编程的好处,用一句话总结就是:面向对象的开发更符合人类的思维习惯,让编程变得更加简单、更加直观。
程序中对象到底是个啥? 对象实质上是一种特殊的数据结构 。这种结构怎么理解呢?
你可以把对象理解成一张表格,表当中记录的数据,就是对象拥有的数据。
一句话总结,对象其实就是一张数据表,表当中记录什么数据,对象就处理什么数据。
对象是怎么出来的? 用什么来设计这张表呢?就是类(class),类可以理解成对象的设计图 ,或者对象的模板。
我们需要按照对象的设计图创造一个对象。设计图中规定有哪些数据,对象中就只能有哪些数据。
一句话总结:对象可以理解成一张数据表,而数据表中可以有哪些数据,是有类来设计的。
执行原理和注意事项
类与对象的一些注意事项。
类名建议用英文单词,首字母大写,满足驼峰模式,且要有意义,比如: Student.Car…
类中定义的变量也称为成员变量(对象的属性),类中定义的方法也称为成员方法(对象的行为).
成员变量本身存在默认值,同学们在定义成员变量时一般来说不需要赋初始值(没有意义)。
一个代码文件中,可以写多个class类,但只能一个用public修饰, 且public修饰的类名必须成为代码文件名。
对象与对象之间的数据不会相互影响,但多个变量指向同一个对象时就会相互影响了。
如果某个对象没有一个变量引用它,则该对象无法被操作了,该对象会成为所谓的垃圾对象。
作用域
基本使用 面向对象中,变量作用域是非常重要知识点,相对来说不是特别好理解,请大家注意听,认真思考,要求深刻掌握变量作用域。
在java编程中,主要的变量就是属性(成员变量)和局部变量。
我们说的局部变量一般是指在成员方法中定义的变量。 举例Cat类: cry
java中作用域的分类 全局变量:也就是属性,作用域为整个类体 Cat类: cry eat等方法使用属性【举例】 局部变量:也就是除了属性之外的其他变量,作用域为定义它的代码块中!
全局变量可以不赋值,直接使用,因为有默认值,局部变量必须赋值后,才能使用,因为没有默认值。
this 关于this关键字我们就学习到这里,重点记住这句话:哪一个对象调用方法方法中的this就是哪一个对象
this关键字是什么?
this就是一个变量,可以用在方法中,用来拿到当前对象;哪个对象调用方法,this就指向哪个对象,也就是拿到哪个对象。
this关键字在实际开发中常用来干啥?
用来解决对象的成员变量与方法内部变量的名称一样时,导致访问冲突问题的。
构造器 有参构造、无参构造
构造器长什么样子?1 2 3 4 5 6 public class Student { public Student () { ... } }
1 2 3 4 5 6 7 8 9 10 11 12 1. 什么是构造器? 答:构造器其实是一种特殊的方法,但是这个方法没有返回值类型,方法名必须和类名相同。 2. 构造器什么时候执行? 答:new 对象就是在执行构造方法; 3. 构造方法的应用场景是什么? 答:在创建对象时,可以用构造方法给成员变量赋值 4. 构造方法有哪些注意事项? 1 )在设计一个类时,如果不写构造器,Java会自动生成一个无参数构造器。 2 )一定定义了有参数构造器,Java就不再提供空参数构造器,此时建议自己加一个无参数构造器。
封装
什么是封装?
就是用类设计对象处理某一个事物的数据时,应该把要处理的数据,以及处理这些数据的方法,
设计到一个对象中去。 面向对象的三大特征:封装、继承、多态。
封装的设计规范是什么样的?
封装的设计规范用8个字总结,就是:合理隐藏、合理暴露
代码层面如何控对象的成员公开或隐藏?
公开成员,可以使用public(公开)进行修饰。
隐藏成员,使用private(私有,隐藏)进行修饰。
实体类
1 2 3 4 5 6 7 1. JavaBean实体类是什么?有啥特点 JavaBean实体类,是一种特殊的;它需要私有化成员变量,有空参数构造方法、同时提供getXxx和setXxx方法; JavaBean实体类仅仅只用来封装数据,只提供对数据进行存和取的方法 2. JavaBean的应用场景? JavaBean实体类,只负责封装数据,而把数据处理的操作放在其他类中,以实现数据和数据处理相分离。
局部变量和成员变量的区别
到这里,我们关于面向对象的基础知识就学习完了。面向对象的核心点就是封装,将数据和数据的处理方式,都封装到对象中; 至于对象要封装哪些数据?对数据进行怎样的处理? 需要通过类来设计。
API 常用API(全称是Application Program Interface 应用程序接口),说人话就是:别人写好的一些程序,给咱们程序员直接拿去调用。
比如String类,表示字符串,提供的方法全都是对字符串操作的。
比如ArrayList类,表示一个容器,提供的方法都是对容器中的数据进行操作的。
包
包是用来分门别类的管理各种不同程序的,类似于文件夹,建包有利于程序的管理和维护。
建包的语法格式:1 2 3 package com.itheima.javabean;public class Student {}
在自己程序中调用其他包下的程序的注意事项
如果当前程序中,要调用自己所在包下的其他程序,可以直接调用。(同一个包下的类,互相可以直接调用 )
如果当前程序中,要调用其他包下的程序,则必须在当前程序中导包,才可以访问!导包格式: import包名.类名 ;
如果当前程序中,要调用Java提供的程序,也需要先导包才可以使用;但是Java.lang包下的程序是不需要我们导包的,可以直接使用。
如果当前程序中,要调用多个不同包下的程序,而这些程序名正好一样,此时默认只能导入一个程序,另一个程序必须带包名访问。
String
String创建对象封装字符串数据的方式
方式一: Java程序中的所有字符串文字(例如“abc”)都为此类的对象。
1 2 String name = "小黑" ;String schoolName="黑马程序员" ;
方式二:调用String类的构造器初始化字符串对象。 构造器 说明 public string() 创建一个空白字符串对象,不含有任何内容 public string(string original) 根据传入的字符串内容,来创建字符串对象 public string(char[] chars) 根据字符数组的内容,来创建字符串对象 public string(byte[] bytes) 根据字节数组的内容,来创建字符串对象
String的注意事项
String对象的内容不可改变,被称为不可 变字符串对象。
只要是以“..”方式写出的字符串对象,会存储到字符串常量池,且相同内容的字符串只存储一份;
但通过new方式创建字符串对象,每new一次都会产生一个新的对象放在堆内存中。
String的应用案例
字符串的比较使用==比较好吗?为什么?什么时候使用==?
不好,对于字符串对象的比较,==比较的是地址,容易出业务bug 基本数据类型的变量或者值应该使用==比较。
开发中比较字符串推荐使用什么方式比较? 使用String提供的equals方法,它只关心字符串内容一样就返回true.
方法名 public boolean equals (object anobject) public boolean equalsIgnoreCase (string anotherString)
ArrayList
集合是什么,有什么特点?
ArrayList是什么?怎么使用?
从集合中遍历元素,并筛选出元素删除它,应该如何操作才能不出bug?
方式一:每次删除一个数据后,索引-1。
方式二:从集合后面遍历然后删除,可以避免漏掉元素。
ArrayList应用案例 我们往集合存储的元素是String类型的元素,实际上在工作中我们经常往集合中自定义存储对象。
接下来我们做个案例,用来往集合中存储自定义的对象,先阅读下面的案例需求:
分析需求发现:
在外卖系统中,每一份菜都包含,菜品的名称、菜品的原价、菜品的优惠价、菜品的其他信息。那我们就可以定义一个菜品类(Food类),用来描述每一个菜品对象要封装那些数据。
接着再写一个菜品管理类(FoodManager类),提供展示操作界面、上架菜品、浏览菜品的功能。
首先我们先定义一个菜品类(Food类),用来描述每一个菜品对象要封装那些数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 public class Food { private String name; private double originalPrice; private double specialPrice; private String info; public Food () { } public Food (String name, double originalPrice, double specialPrice, String info) { this .name = name; this .originalPrice = originalPrice; this .specialPrice = specialPrice; this .info = info; } public String getName () { return name; } public void setName (String name) { this .name = name; } public double getOriginalPrice () { return originalPrice; } public void setOriginalPrice (double originalPrice) { this .originalPrice = originalPrice; } public double getSpecialPrice () { return specialPrice; } public void setSpecialPrice (double specialPrice) { this .specialPrice = specialPrice; } public String getInfo () { return info; } public void setInfo (String info) { this .info = info; } }
接下来写一个菜品管理类,提供上架菜品的功能、浏览菜品的功能、展示操作界面的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 public class FoodManager { private ArrayList<Food> foods = new ArrayList <>(); private Scanner sc = new Scanner (System.in); public void add () { System.out.println("===菜品上架==" ); Food food = new Food (); System.out.println("请您输入上架菜品的名称:" ); String name = sc.next(); food.setName(name); System.out.println("请您输入上架菜品的原价:" ); double originalPrice = sc.nextDouble(); food.setOriginalPrice(originalPrice); System.out.println("请您输入上架菜品的优惠价:" ); double specialPrice = sc.nextDouble(); food.setSpecialPrice(specialPrice); System.out.println("请您输入上架菜品的其他信息:" ); String info = sc.next(); food.setInfo(info); foods.add(food); System.out.println("恭喜您,上架成功~~~" ); } public void printAllFoods () { System.out.println("==当前菜品信息如下:==" ); for (int i = 0 ; i < foods.size(); i++) { Food food = foods.get(i); System.out.println("菜品名称:" + food.getName()); System.out.println("菜品原价:" + food.getOriginalPrice()); System.out.println("菜品优惠价:" + food.getSpecialPrice()); System.out.println("其他描述信息:" + food.getInfo()); System.out.println("------------------------" ); } } public void start () { while (true ) { System.out.println("====欢迎进入商家后台管理系统=====" ); System.out.println("1、上架菜品(add)" ); System.out.println("2、浏览菜品(query)" ); System.out.println("3、退出系统(exit)" ); System.out.println("请您选择操作命令:" ); String command = sc.next(); switch (command) { case "add" : add(); break ; case "query" : printAllFoods(); break ; case "exit" : return ; default : System.out.println("您输入的操作命令有误~~" ); } } } }
最后在写一个测试类Test,在测试类中进行测试。其实测试类,只起到一个启动程序的作用。
1 2 3 4 5 6 public class Test { public static void main (String[] args) { FoodManager manager = new FoodManager (); manager.start(); } }
运行结果如下:需要用户输入add、query或者exit,选择进入不同的功能。
能够把这个案例写出来,说明对面向对象的思维封装数据,以及使用ArrayList容器存储数据,并对数据进行处理这方面的知识已经运用的很熟悉了。
ATM案例 一、ATM项目介绍 ATM系统功能介绍
大家都应该去过银行的ATM机上取过钱,每次取钱的时候,首先需要用户把卡插入机器,然后机器会自动读取你的卡号,由用户输入密码,如果密码校验通过,就会进入ATM机的主操作界面:有查询、取款、存款、转账等业务功能 ,用户选择哪个功能就执行对应预先设定好的程序。
二、项目架构搭建、欢迎界面设计 接下来,我们带着大家开始开发这个ATM系统。首先我们来完成项目的架构搭建、和欢迎界面的设计。
首先我们来分析一下,开发这个ATM系统的流程:
由于每一个账户都包含一些个人信息,比如:卡号、姓名、性别、密码、余额、每次取现额度等等。所以,首先可以设计一个Account类,用来描述账户对象需要封装那些数据。
紧接着,定义一个ATM类,用来表示ATM系统,负责提供所有的业务需求。
比如:展示ATM系统的欢迎页面、开户、登录、转账等功能。
最后,定义一个测试类Test,负责启动我们开发好的ATM系统,进行测试。
第一步:先来完成Account类的编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class Account { private String cardId; private String userName; private char sex; private String passWord; private double money; private double limit; public String getCardId () { return cardId; } public void setCardId (String cardId) { this .cardId = cardId; } public String getUserName () { return userName + ( sex == '男' ? "先生" : "女士" ); } public void setUserName (String userName) { this .userName = userName; } public char getSex () { return sex; } public void setSex (char sex) { this .sex = sex; } public String getPassWord () { return passWord; } public void setPassWord (String passWord) { this .passWord = passWord; } public double getMoney () { return money; } public void setMoney (double money) { this .money = money; } public double getLimit () { return limit; } public void setLimit (double limit) { this .limit = limit; } }
第二步:编写一个ATM类,负责对每一个账户对象进行管理
1 2 3 4 public class ATM { private ArrayList<Account> accounts = new ArrayList <>(); }
第三步:在ATM类中,编写欢迎界面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class ATM { private ArrayList<Account> accounts = new ArrayList <>(); private Scanner sc = new Scanner (System.in); public void start () { while (true ) { System.out.println("===欢迎您进入到了ATM系统===" ); System.out.println("1、用户登录" ); System.out.println("2、用户开户" ); System.out.println("请选择:" ); int command = sc.nextInt(); switch (command){ case 1 : System.out.println("进入登录功能" ); break ; case 2 : System.out.println("进入开户功能" ); break ; default : System.out.println("没有该操作~~" ); } } } }
三、开户功能实现 接下来,我们完成开户功能 的实现。需求如下:
为了系统的代码结构更加清晰,在ATM类中,写一个开户的方法。
步骤如下:
1、创建一个账户对象,用于封装用户的开户信息
2、需要用户输入自己的开户信息,赋值给账户对象
输入账户名,设置给账户对象
输入性别,如果性别是'男'
或者'女'
,将性别设置给账户对象;否则重新录入性别知道录入正确为止。
输入账户、并且输入两次密码,只有两次密码相同,才将账户和密码设置给账户对象。
输入提现限额,并且设置给账户对象
3、输出开户成功,的提示语句。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 private void createAccount () { System.out.println("==系统开户操作==" ); Account acc = new Account (); System.out.println("请您输入您的账户名称:" ); String name = sc.next(); acc.setUserName(name); while (true ) { System.out.println("请您输入您的性别:" ); char sex = sc.next().charAt(0 ); if (sex == '男' || sex == '女' ){ acc.setSex(sex); break ; }else { System.out.println("您输入的性别有误~只能是男或者女~" ); } } while (true ) { System.out.println("请您输入您的账户密码:" ); String passWord = sc.next(); System.out.println("请您输入您的确认密码:" ); String okPassWord = sc.next(); if (okPassWord.equals(passWord)){ acc.setPassWord(okPassWord); break ; }else { System.out.println("您输入的2次密码不一致,请您确认~~" ); } } System.out.println("请您输入您的取现额度:" ); double limit = sc.nextDouble(); acc.setLimit(limit); accounts.add(acc); System.out.println("恭喜您," + acc.getUserName() + "开户成功,您的卡号是:" + acc.getCardId()); }
因为生成卡号比较麻烦,所以在下一节,我们单独来写一个方法用于生成卡号。
四、生成卡号
第一步:先在ATM类中,写一个判断卡号是否存在的功能。
1 2 3 4 5 6 7 8 9 10 11 12 private Account getAccountByCardId (String cardId) { for (int i = 0 ; i < accounts.size(); i++) { Account acc = accounts.get(i); if (acc.getCardId().equals(cardId)){ return acc; } } return null ; }
第二步:再在ATM类中,写一个生成卡号的功能
1、先随机产生8个[0,9]范围内的随机数,拼接成一个字符串
2、然后再调用getAccountByCardId方法,判断这个卡号字符串是否存在
3、判断生成的卡号是否存在
如果生成的卡号不存在,说明生成的卡号是有效的,把卡号返回,
如果生成的卡号存在,说明生成的卡号无效,循环继续生产卡号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 private String createCardId () { while (true ) { String cardId = "" ; Random r = new Random (); for (int i = 0 ; i < 8 ; i++) { int data = r.nextInt(10 ); cardId += data; } Account acc = getAccountByCardId(cardId); if (acc == null ){ return cardId; } } }
写完生成卡号的功能后,在开户功能的TODO
位置,调用生成卡号的功能,并且将生成的卡号设置到账户对象中。
五、登录功能 在上面我们已经完成了开户功能。接下来我们来编写登录功能,编写登录功能的时候我们要满足一下需求:
① 如果系统没有任何账户对象,则不允许登录。
② 让用户输入登录的卡号,先判断卡号是否正确,如果不正确要给出提示。
③ 如果卡号正确,再让用户输入账户密码,如果密码不正确要给出提示,如果密码也正确,则给出登录成功的提示。
登录功能具体实现步骤如下:
1、判断系统中是否存在账户对象,存在才能登录,如果不存在,我们直接结束登录操作
2、输入登录的卡号,并判断卡号是否存在
3、如果卡号不存在,直接给出提示
4、如果卡号存在,接着输入用户密码,并判断密码是否正确
5、如果密码也正确,则登录成功,并且记录当前的登录账户
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 private void login () { System.out.println("==系统登录==" ); if (accounts.size() == 0 ){ System.out.println("当前系统中无任何账户,请先开户再来登录~~" ); return ; } while (true ) { System.out.println("请您输入您的登录卡号:" ); String cardId = sc.next(); Account acc = getAccountByCardId(cardId); if (acc == null ){ System.out.println("您输入的登录卡号不存在,请确认~~" ); }else { while (true ) { System.out.println("请您输入登录密码:" ); String passWord = sc.next(); if (acc.getPassWord().equals(passWord)){ loginAcc = acc; System.out.println("恭喜您," + acc.getUserName() + "成功登录了系统,您的卡号是:" + acc.getCardId()); return ; }else { System.out.println("您输入的密码不正确,请确认~~" ); } } } } }
六、展示用户操作界面 登录成功之后,需要显示登录后的用户操作界面。效果如下
写成一个方法,用来展示登录成功的操作界面,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 private void showUserCommand () { while (true ) { System.out.println(loginAcc.getUserName() + "您可以选择如下功能进行账户的处理====" ); System.out.println("1、查询账户" ); System.out.println("2、存款" ); System.out.println("3、取款" ); System.out.println("4、转账" ); System.out.println("5、密码修改" ); System.out.println("6、退出" ); System.out.println("7、注销当前账户" ); System.out.println("请选择:" ); int command = sc.nextInt(); switch (command){ case 1 : break ; case 2 : break ; case 3 : break ; case 4 : break ; case 5 : return ; case 6 : System.out.println(loginAcc.getUserName() + "您退出系统成功!" ); return ; case 7 : if (deleteAccount()){ return ; } break ; default : System.out.println("您当前选择的操作是不存在的,请确认~~" ); } } }
写好用户操作界面的方法之后,再到登录成功的位置调用,登录成功后,马上显示用户操作界面。刚才在哪里打了一个TODO
标记的,回去找找。
到这里,登录功能就写好了。
六、查询账户、退出
查询账户:在用户操作界面,选择1查询当前账户信息。效果如下:
登录成功的时候,已经把当前账户对象用一个成员变量存储了 ,所以直接按照如下格式打印账户对象的属性信息即可。
这里也写成一个方法
1 2 3 4 5 6 7 8 9 10 11 private void showLoginAccount () { System.out.println("==当前您的账户信息如下:==" ); System.out.println("卡号:" + loginAcc.getCardId()); System.out.println("户主:" + loginAcc.getUserName()); System.out.println("性别:" + loginAcc.getSex()); System.out.println("余额:" + loginAcc.getMoney()); System.out.println("每次取现额度:" + loginAcc.getLimit()); }
退出功能:其实就是将ATM系统中,在用户界面选择6时,直接结束程序。
七、存款 接下来来完成存款操作。
我们把存款功能也写成一个方法,具体步骤如下:
键盘录入要存入的金额
在原有余额的基础上,加上存入金额,得到新的余额
再将新的余额设置给当前账户对象
1 2 3 4 5 6 7 8 9 10 private void depositMoney () { System.out.println("==存钱操作==" ); System.out.println("请您输入存款金额:" ); double money = sc.nextDouble(); loginAcc.setMoney(loginAcc.getMoney() + money); System.out.println("恭喜您,您存钱:" + money + "成功,存钱后余额是:" + loginAcc.getMoney()); }
写好存款的方法之后,在case 2:
的下面调用depositMoney()
方法
到这里,存款功能就写好了。
八、取款 接下来我们写一下取款的功能。
把取款的功能也写成一个方法,具体步骤如下
按照上面分析的步骤,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 private void drawMoney () { System.out.println("==取钱操作==" ); if (loginAcc.getMoney() < 100 ){ System.out.println("您的账户余额不足100元,不允许取钱~~" ); return ; } while (true ) { System.out.println("请您输入取款金额:" ); double money = sc.nextDouble(); if (loginAcc.getMoney() >= money){ if (money > loginAcc.getLimit()){ System.out.println("您当前取款金额超过了每次限额,您每次最多可取:" + loginAcc.getLimit()); }else { loginAcc.setMoney(loginAcc.getMoney() - money); System.out.println("您取款:" + money + "成功,取款后您剩余:" + loginAcc.getMoney()); break ; } }else { System.out.println("余额不足,您的账户中的余额是:" + loginAcc.getMoney()); } } }
写好取钱方法之后,在case 3:
的位置调用drawMoney()
方法
九、转账 接下来我们来编写转账的功能。转账的意思就是,将一个账户的钱转入另一个账,具体的转账逻辑如下:
我们把转账功能也写成一个方法
1、判断系统中是否存在其他账户
2、判断自己的账户中是否有钱
3、真正开始转账了,输入对方卡号
4、判断对方卡号是否正确啊?
5、如果卡号正确,就继续让用户输入姓氏, 并判断这个姓氏是否正确?
如果姓氏不正确,给出提示“对不起,您姓氏有问题,转账失败!”
6、如果姓氏正确,继续判断这个转账金额是否超过自己的余额。
如果转账金额超过余额,给出提示“对不起,余额不足,转账失败!”
7、如果对方卡号存在、姓氏匹配、余额足够,就完成真正的转账操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 private void transferMoney () { System.out.println("==用户转账==" ); if (accounts.size() < 2 ){ System.out.println("当前系统中只有你一个账户,无法为其他账户转账~~" ); return ; } if (loginAcc.getMoney() == 0 ){ System.out.println("您自己都没钱,就别转了~~" ); return ; } while (true ) { System.out.println("请您输入对方的卡号:" ); String cardId = sc.next(); Account acc = getAccountByCardId(cardId); if (acc == null ){ System.out.println("您输入的对方的卡号不存在~~" ); }else { String name = "*" + acc.getUserName().substring(1 ); System.out.println("请您输入【" + name + "】的姓氏:" ); String preName = sc.next(); if (acc.getUserName().startsWith(preName)) { while (true ) { System.out.println("请您输入转账给对方的金额:" ); double money = sc.nextDouble(); if (loginAcc.getMoney() >= money){ loginAcc.setMoney(loginAcc.getMoney() - money); acc.setMoney(acc.getMoney() + money); System.out.println("您转账成功了~~~" ); return ; }else { System.out.println("您余额不足,无法给对方转这么多钱,最多可转:" + loginAcc.getMoney()); } } }else { System.out.println("对不起,您认证的姓氏有问题~~" ); } } } }
写好修改转账功能之后,在case 4:
这里调用。如下:
到这里,转账功能就写好了。
十、修改密码 接下来我们完成修改密码的功能。
把修改密码的功能也是写成一个方法,具体步骤如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 private void updatePassWord () { System.out.println("==账户密码修改操作==" ); while (true ) { System.out.println("请您输入当前账户的密码:" ); String passWord = sc.next(); if (loginAcc.getPassWord().equals(passWord)){ while (true ) { System.out.println("请您输入新密码:" ); String newPassWord = sc.next(); System.out.println("请您再次输入密码:" ); String okPassWord = sc.next(); if (okPassWord.equals(newPassWord)){ loginAcc.setPassWord(okPassWord); System.out.println("恭喜您,您的密码修改成功~~~" ); return ; }else { System.out.println("您输入的2次密码不一致~~" ); } } }else { System.out.println("您当前输入的密码不正确~~" ); } } }
写好修改密码的功能之后。在case 5:
的位置调用updatePassWord()
方法。如下图所示
好了,到这里修改密码的功能就写好了。
十一、注销 各位同学,接下来我们完成最后一个功能,注销功能。
这里把注销功能也写成一个方法,具体步骤如下
1、先确认是否需要注销账户,让用户输入y或者n
如果输入y,表示确认
如果输入n,表示取消注销操作
2、输入y后,继续判断当前用户的账户是否有钱
如果账户有钱,提示:“对不起,您的账户中存钱金额,不允许销”
如果账户没有钱,则把当前账户对象,从系统的集合中删除,完成注销。
按照上面的步骤代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private boolean deleteAccount () { System.out.println("==进行销户操作==" ); System.out.println("请问您确认销户吗?y/n" ); String command = sc.next(); switch (command) { case "y" : if (loginAcc.getMoney() == 0 ) { accounts.remove(loginAcc); System.out.println("您好,您的账户已经成功销户~~" ); return true ; }else { System.out.println("对不起,您的账户中存钱金额,不允许销户~~" ); return false ; } default : System.out.println("好的,您的账户保留!!" ); return false ; } }
注销功能写好之后,在用户操作界面的case 7:
位置调用deleteAccount()
的方法。
到这里注销账户的功能就写好了。
面向对象高级 前面我们说过面向对象最核心的套路是:设计对象来处理数据,解决问题。
静态static
static是什么?
static修饰的成员变量叫什么?怎么使用?有啥特点?
类变量(静态成员变量) 类名.类变量(推荐) 对象名.类变量(不推荐)
属于类,与类一起加载一次,在内存中只有一份,会被类的所有对象共享。
无static修饰的成员变量叫什么?怎么使用?有啥特点?
实例变量(对象变量) 对象.实例变量
属于对象,每个对象中都有一份。
成员变量有几种?各自在什么情况下定义?
类变量:数据只需要一份,且需要被共享时(访问,修改) 实例变量:每个对象都要有一份,数据各不同(如:name、score、age)
访问自己类中的类变量,是否可以省略类名不写? 可以的 注意:在某个类中访问其他类里的类变量,必须带类名访问
成员方法的分类
1 2 3 4 static void printHellowor1d () {system.out.println("He1lo wor1d!" ); system.out.printin("Hello wor1d!" ); }
类名.类方法(推荐) student.printHelloWorld; 对象名.类方法(不推荐) s1.printHelloWorld;
实例方法︰无static修饰的成员方法,属于对象。 pubiic void printPass() 对象.实例方法
工具类 如果一个类中的方法全都是静态的,那么这个类中的方法就全都可以被类名直接调用,由于调用起来非常方便,就像一个工具一下,所以把这样的类就叫做工具类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class MyUtils { public static String createCode (int n) { String code = "" ; String data = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKMNOPQRSTUVWXYZ" ; Random r = new Random (); for (int i=0 ; i<n; i++){ int index = r.nextInt(data.length()); char ch = data.charAt(index); code+=ch; } return code; } }
接着可以在任何位置调用MyUtils
的createCOde()方法
产生任意个数的验证码
1 2 3 4 5 6 public class LoginDemo { public static void main (String[] args) { System.out.println(MyUtils.createCode()); } }
1 2 3 4 5 6 public class registerDemo { public static void main (String[] args) { System.out.println(MyUtils.createCode()); } }
在补充一点,工具类里的方法全都是静态的,推荐用类名调用为了防止使用者用对象调用。我们可以把工具类的构造方法私有化。
1 2 3 4 5 6 7 8 9 10 11 public class MyUtils { private MyUtils () { } public static String createCode (int n) { ... } }
类方法有啥应用场景? 可以用来设计工具类。
工具类是什么,有什么好处? 工具类中的方法都是类方法,每个类方法都是用来完成一个功能的。 提高了代码的复用性;调用方便,提高了开发效率。
为什么工具类要用类方法,而不是用实例方法? 实例方法需要创建对象来调用,会浪费内存。
工具类定义时有什么要求? 工具类不需要创建对象,建议将工具类的构造器私有化。
static的注意事项
类方法中可以直接访问类的成员,不可以直接访问实例成员 。
实例方法中既可以真接访问类成员,也可以直接访问实例成员。
实例方法中可以出现this关键字,类方法中不可以出现this关键字的。
代码块
代码块是类的5大成分之一(成员变量、构造器、方法、代码块、内部类)。
代码块分为两种;
静态代码块:
格式: static { }
特点:类加载时自动执行,由于类只会加载一次,所以静态代码块也只会执行一次。
作用:完成类的初始化,例如:对类变量的初始化赋值。
实例代码块:
格式:{}
特点:每次创建对象时,执行实例代码块,并在构造器前执行。
作用:和构造器一样,都是用来完成对象的初始化的,例如:对实例变量进行初始化赋值。
我们先类学习静态代码块:
1 2 3 4 5 6 7 8 9 public class Student { static int number = 80 ; static String schoolName = "黑马" ; static { System.out.println("静态代码块执行了~~" ); schoolName = "黑马" ; } }
静态代码块不需要创建对象就能够执行
1 2 3 4 5 6 7 8 9 10 public class Test { public static void main (String[] args) { System.out.println(Student.number); System.out.println(Student.number); System.out.println(Student.number); System.out.println(Student.schoolName); } }
执行上面代码时,发现没有创建对象,静态代码块就已经执行了。
关于静态代码块重点注意:静态代码块,随着类的加载而执行,而且只执行一次。
再来学习一下实例代码块
实例代码块的作用和构造器的作用是一样的,用来给对象初始化值;而且每次创建对象之前都会先执行实例代码块。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Student { int age; { System.out.println("实例代码块执行了~~" ); age = 18 ; System.out.println("有人创建了对象:" + this ); } public Student () { System.out.println("无参数构造器执行了~~" ); } public Student (String name) { System.out.println("有参数构造器执行了~~" ); } }
接下来在测试类中进行测试,观察创建对象时,实例代码块是否先执行了。
1 2 3 4 5 6 7 8 public class Test { public static void main (String[] args) { Student s1 = new Student (); Student s2 = new Student ("张三" ); System.out.println(s1.age); System.out.println(s2.age); } }
对于实例代码块重点注意:实例代码块每次创建对象之前都会执行一次
设计模式
什么是设计模式,设计模式主要学什么?单例模式解决了什么问题?
设计模式就是具体问题的最优解决方案。
解决了什么问题?怎么写?
确保一个类只有一个对象。
单例怎么写?饿汉式单例的特点是什么?
把类的构造器私有;定义一个类变量存储类的一个对象;提供一个类方法返回对象。
在获取类的对象时,对象已经创建好了。
单例有啥应用场景,有啥好处?
任务管理器对象、获取运行时对象。
在这些业务场景下,使用单例模式,可以避免浪费内存。
把类的构造器私有。
定义一个类变量用于存储对象。
提供一个类方法,保证返回的是同一个对象。
继承
接下来,我们演示一下使用继承来编写代码,注意观察继承的特点。
1 2 3 4 5 6 7 8 9 10 11 12 13 public class A { public int i; public void print1 () { System.out.println("===print1===" ); } private int j; private void print2 () { System.out.println("===print2===" ); } }
然后,写一个B类,让B类继承A类。在继承A类的同时,B类中新增一个方法print3
1 2 3 4 5 6 7 8 9 10 11 public class B extends A { public void print3 () { System.out.println(i); print1(); System.out.println(j); print2(); } }
接下来,我们再演示一下,创建B类对象,能否调用父类A的成员。再写一个测试类
1 2 3 4 5 6 7 8 9 10 11 12 public class Test { public static void main (String[] args) { B b = new B (); System.out.println(i); b.print1(); System.out.println(j); b.print2(); } }
到这里,关于继承的基本使用我们就算学会了。为了让大家对继承有更深入的认识,我们来看看继承的内存原理。
这里我们只需要关注一点:子类对象实际上是由子、父类两张设计图共同创建出来的。
所以,在子类对象的空间中,既有本类的成员,也有父类的成员。但是子类只能调用父类公有的成员
什么是继承?继承后有啥特点?
继承就是用extends关键字,让一个类和另一个类建立起一种父子关系。
子类可以继承父类非私有的成员。
带继承关系的类,Java会怎么创建它的对象?对象创建出来后,可以直接访问哪些成员?
带继承关系的类,java会用类和其父类,这多张设计图来一起创建类的对象。
对象能直接访问什么成员,是由子父类这多张设计图共同决定的,这多张设计图对外暴露了什么成员,对象就可以访问什么成员。
权限修饰符 使用继承编写的代码中我们有用到两个权限修饰符,一个是public(公有的)、一个是private(私有的),实际上还有两个权限修饰符,一个是protected(受保护的)、一个是缺省的(不写任何修饰符)。
什么是权限修饰符呢?
权限修饰符是用来限制类的成员(成员变量、成员方法、构造器…)能够被访问的范围。
每一种权限修饰符能够被访问的范围如下
单继承、Object Java语言只支持单继承,不支持多继承,但是可以多层继承 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Test { public static void main (String[] args) { A a = new A (); B b = new B (); ArrayList list = new ArrayList (); list.add("java" ); System.out.println(list.toString()); } } class A {} class B extends A {}class D extends B {}
继承相关的两个注意事项?
Java是单继承的:一个类只能继承一个直接父类;Java中的类不支持多继承,但是支持多层继承。
object类是Java中所有类的祖宗。
方法重写
什么是方法重写
当子类觉得父类方法不好用,或者无法满足父类需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。
注意:重写后,方法的访问遵循就近原则
什么是方法重写?
当子类觉得父类中的某个方法不好用,或者无法满足自己的需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。
注意:重写后,方法的访问,Java会遵循就近原则。
方法重写的其它注意事项
1 2 3 4 5 - 1. 重写的方法上面,可以加一个注解@Override ,用于标注这个方法是复写的父类方法 - 2. 子类复写父类方法时,访问权限必须大于或者等于父类方法的权限 public > protected > 缺省 - 3. 重写的方法返回值类型,必须与被重写的方法返回值类型一样,或者范围更小 - 4. 私有方法、静态方法不能被重写,如果重写会报错。
子类访问其他成员特点
原则:在子类中访问其他成员(成员变量、成员方法),是依据就近原则的
在子类方法中访问其他成员(成员变量、成员方法),是依照就近原则的。
先子类局部范围找。
然后子类成员范围找。
然后父类成员范围找,如果父类范围还没有找到则报错。
如果子父类中,出现了重名的成员,会优先使用子类的,如果此时一定要在子类中使用父类的怎么办?
可以通过super关键字,指定访问父类的成员: super.父类成员变量/父类成员方法
子类构造器的特点
子类构造器的特点:
子类的全部构造器,都会先调用父类的构造器,再执行自己。
子类构造器是如何实现调用父类构造器的:
默认情况下,子类全部构造器的第一行代码都是super()(写不写都有),它会调用父类的无参数构造器。
如果父类没有无参数构造器,则我们必须在子类构造器的第一行手写super(…),指定去调用父类的有参数构造器。
补充知识:this(..)调用兄弟构造器
任意类的构造器中,是可以通过this(…)去调用该类的其他构造器的。1 2 3 4 5 6 7 8 9 10 11 12 public class student { private string schoolName; private string name; public student (string name) { this (name ,“黑马程序员”); } public student (string name , string schoolName ) { this .name = name; this .schoolName = schoolName; } }
this(…)和super(…)使用时的注意事项:
this(…) 、 super(..)都只能放在构造器的第一行,因此,有了this(…)就不能写super(…)了,反之亦然。
最后我们被this和super的用法在总结一下
1 2 3 4 5 6 7 8 9 10 11 12 13 访问本类成员: this .成员变量 this .成员方法 this () this (参数) 访问父类成员: super .成员变量 super .成员方法 super () super (参数) 注意:this 和super 访问构造方法,只能用到构造方法的第一句,否则会报错。
多态
什么是多态?
多态是在继承、实现情况下的一种现象,表现为:对象多态、行为多态。
有继承/实现关系;存在父类引用子类对象;存在方法重写。多态的一个注意事项
多态是对象、行为的多态,Java中的属性(成员变量)不谈多态。
定义方法时,使用父类类型作为形参,可以接收一切子类对象,扩展行更强,更便利。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Test2 { public static void main (String[] args) { Teacher t = new Teacher (); go(t); Student s = new Student (); go(s); } public static void go (People p) { System.out.println("开始------------------------" ); p.run(); System.out.println("结束------------------------" ); } }
使用多态有什么好处?存在什么问题? 可以解耦合,扩展性更强;使用父类类型的变量作为方法的形参时,可以接收一切子类对象。
类型转换
多态形式下不能直接调用子类特有方法,但是转型后是可以调用的。这里所说的转型就是把父类变量转换为子类类型。格式如下:
1 2 3 4 5 if (父类变量 instance 子类){ 子类 变量名 = (子类)父类变量; }
存在继承/实现关系就可以在编译阶段进行强制类型转换,编译阶段不会报错。
运行时,如果发现对象的真实类型与强转后的类型不同,就会报类型转换异常
强转前,Java建议:
使用instanceof关键字,判断当前对象的真实类型,再进行强转。 p instanceof Student
final
final关键字是最终的意思,可以修饰(类、方法、变量)
1 2 3 - final 修饰类:该类称为最终类,特点是不能被继承 - final 修饰方法:该方法称之为最终方法,特点是不能被重写。 - final 修饰变量:该变量只能被赋值一次。
final修饰基本类型的变量,变量存储的数据不能被改变。
final修饰引用类型的变量,变量存储的地址不能被改变 但地址所指向对象的内容是可以被改变的。
常量
被 static final 修饰的成员变量,称之为常量。
通常用于记录系统的配置信息
1 2 3 4 5 6 public class Constant { public static final String SCHOOL_NAME = "教育" ; }
注意!常量名的命名规范:建议使用大写英文单词,多个单词使用下划线连接起来。
代码可读性更好,可维护性也更好。
程序编译后,常量会被“宏替换”︰出现常量的地方全部会被替换成其记住的字面量, 这样可以保证使用常量和直接用字面量的性能是一样的。
抽象类
在Java中有一个关键字叫abstract,它就是抽象的意思,它可以修饰类也可以修饰方法。
1 2 - 被abstract 修饰的类,就是抽象类 - 被abstract 修饰的方法,就是抽象方法(不允许有方法体)
类的成员(成员变量、成员方法、构造器),类的成员都可以有。如下面代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public abstract class A { private String name; static String schoolName; public A () { } public abstract void test () ; public String getName () { return name; } public void setName (String name) { this .name = name; } }
1 2 3 4 5 6 7 public class B extends A { @Override public void test () { } }
子类继承父类如果不复写父类的抽象方法,要想不出错,这个子类也必须是抽象类
1 2 3 4 public abstract class B extends A {}
注意:抽象类不能创建对象
抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类。
类该有的成员(成员变量、方法、构造器)抽象类都可以有。
抽象类最主要的特点 :抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现。
一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
抽象类的应用场景和好处是什么? 父类知道每个子类都要做某个行为但每个子类要做的情况不一样,父类就定义成抽象方法,交给子类去重身实现,我们抽出这样的抽象类,就是为了更好的支持多态。
模拟方法设计模式 学习完抽象类的语法之后,接下来,我们学习一种利用抽象类实现的一种设计模式。先解释下一什么是设计模式?设计模式是解决某一类问题的最优方案 。
设计模式在一些源码中经常会出现,还有以后面试的时候偶尔也会被问到,所以在合适的机会,就会给同学们介绍一下设计模式的知识。
那模板方法设计模式解决什么问题呢?模板方法模式主要解决方法中存在重复代码的问题
比如A类和B类都有sing()方法,sing()方法的开头和结尾都是一样的,只是中间一段内容不一样。此时A类和B类的sing()方法中就存在一些相同的代码。
怎么解决上面的重复代码问题呢? 我们可以写一个抽象类C类,在C类中写一个doSing()的抽象方法。再写一个sing()方法,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 public abstract class C { public final void sing () { System.out.println("唱一首你喜欢的歌:" ); doSing(); System.out.println("唱完了!" ); } public abstract void doSing () ; }
然后,写一个A类继承C类,复写doSing()方法,代码如下
1 2 3 4 5 6 public class A extends C { @Override public void doSing () { System.out.println("我是一只小小小小鸟,想要飞就能飞的高~~~" ); } }
接着,再写一个B类继承C类,也复写doSing()方法,代码如下
1 2 3 4 5 6 public class B extends C { @Override public void doSing () { System.out.println("我们一起学猫叫,喵喵喵喵喵喵喵~~" ); } }
最后,再写一个测试类Test
1 2 3 4 5 6 7 public class Test { public static void main (String[] args) { B b = new B (); b.sing(); } }
综上所述:模板方法模式解决了多个子类中有相同代码的问题。具体实现步骤如下
1 2 3 第1 步:定义一个抽象类,把子类中相同的代码写成一个模板方法。 第2 步:把模板方法中不能确定的代码写成抽象方法,并在模板方法中调用。 第3 步:子类继承抽象类,只需要父类抽象方法就可以了。
模板方法设计模式解决了什么问题?
模板方法设计模式应该怎么写?
定义一个抽象类。
在里面定义2个方法, 一个是模板方法:放相同的代码里, 一个是抽象方法:具体实现交给子类完成。
模板方法建议使用什么关键字修饰?为什么
接口 认识接口
Java提供了一个关键字interface,用这个关键字我们可以定义出一个特殊的结构∶接口。
1 2 3 4 public interface 接口名{ }
那接口到底什么使用呢?需要我注意下面两点
修饰符 class 实现类 implements接口1,接口2,接口3,… { }
使用接口有啥好处,第一个好处是什么?
可以解决类单继承的问题,通过接口,我们可以让一个类有一个亲爹的同时,还可以找多个干爹去扩展自己的功能。
为什么我们要通过接口,也就是去找干爹,来扩展自己的功能呢?
因为通过接口去找干爹,别人通过你implements的接口,就可以显性的知道你是谁,从而也就可以放心的把你当作谁来用了。
使用接口的第二个好处是什么?
一个类我们说可以实现多个接口,同样,一个接口也可以被多个类实现的。这样做的好处是我们的程序就可以面向接口编程了,这样我们程序员就可以很方便的灵活切换各种业务实现了。
JDK8新增方法 我们看一下这三种方法分别有什么特点?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public interface A { default void test1 () { System.out.println("===默认方法==" ); test2(); } private void test2 () { System.out.println("===私有方法==" ); } static void test3 () { System.out.println("==静态方法==" ); } void test4 () ; void test5 () ; default void test6 () { } }
接下来我们写一个B类,实现A接口。B类作为A接口的实现类,只需要重写抽象方法就尅了,对于默认方法不需要子类重写。代码如下:
1 2 3 4 5 6 7 8 9 10 11 public class B implements A { @Override public void test4 () { } @Override public void test5 () { } }
最后,写一个测试类,观察接口中的三种方法,是如何调用的
1 2 3 4 5 6 7 8 9 public class Test { public static void main (String[] args) { B b = new B (); b.test1(); A.test3(); } }
综上所述:JDK8对接口新增的特性,有利于对程序进行扩展。
JDK8开始,接口中新增了哪些方法?
默认方法:使用default修饰,使用实现类的对象调用。
静态方法:static修饰,必须用当前接口名调用
私有方法:private修饰,jdk9开始才有的,只能在接口内部被调用。
他们都会默认被public修饰。
JDK8开始,接口中为啥要新增这些方法?
接口的多继承
接口其他注意事项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class Test { public static void main (String[] args) { } } interface A { void test1 () ; } interface B { void test2 () ; } interface C {}interface D extends C , B, A{} class E implements D { @Override public void test1 () { } @Override public void test2 () { } }
接口除了上面的多继承特点之外,在多实现、继承和实现并存时,有可能出现方法名冲突的问题,需要了解怎么解决(仅仅只是了解一下,实际上工作中几乎不会出现这种情况)
1 2 3 4 1. 一个接口继承多个接口,如果多个接口中存在相同的方法声明,则此时不支持多继承2. 一个类实现多个接口,如果多个接口中存在相同的方法声明,则此时不支持多实现3. 一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会有限使用父类的方法4. 一个类实现类多个接口,多个接口中有同名的默认方法,则这个类必须重写该方法。
内部类 内部类是类中的五大成分之一(成员变量、方法、构造器、内部类、代码块),如果一个类定义在另一个类的内部,这个类就是内部类。
当一个类的内部,包含一个完整的事物,且这个事物没有必要单独设计时,就可以把这个事物设计成内部类。
内部类有四种形式,分别是成员内部类、静态内部类、局部内部类、匿名内部类。
就是类中的一个普通成员,类似前面我们学过的普通的成员变量、成员方法。
1 2 3 4 5 public class Outer { public class Inner { } }
注意:JDK16之前,成员内部类中不能定义静态成员,JDK 16开始也可以定义静态成员了
创建对象的格式: 外部类名.内部类名 对象名= new 外部类(…).new 内部类(…); outer.Inner in = new Outer( ).new Inner( );
成员内部类是什么?如何创建其对象?
就是类中的一个普通成员,类似前面我们学过的普通成员变量、成员方法
外部类名.内部类名对象名=new外部类(…).new内部类(.…);
成员内部类的实例方法中,访问其他成员有啥特点?
可以直接访问外部类的实例成员、静态成员
可以拿到当前外部类对象,格式是:外部类名.this。
静态内部类
1 2 3 4 5 public class Outer { public static class Inner { } }
静态内部类创建对象时,需要使用外部类的类名调用。
1 2 3 Outer.Inner in = new Outer .Inner(); in.test();
可以直接访问外部类的静态成员,不可以直接访问外部类的实例成员。
局部内部类 局部内部类是定义在方法中的类,和局部变量一样,只能在方法中有效。所以局部内部类的局限性很强,一般在开发中是不会使用的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Outer { public void test () { class Inner { public void show () { System.out.println("Inner...show" ); } } Inner in = new Inner (); in.show(); } }
匿名内部类
我们还是先认识一下什么是匿名内部类?
匿名内部类是一种特殊的局部内部类;所谓匿名,指的是程序员不需要为这个类声明名字。
匿名内部类
就是一种特殊的局部内部类;所谓匿名:指的是程序员不需要为这个类声明名字。
下面就是匿名内部类的格式:
1 2 3 4 new 父类/接口(参数值){ @Override 重写父类/接口的方法; }
特点:匿名内部类本质就是一个子类,并会立即创建出一个子类对象。
作用:用于更方便的创建一个子类对象。
匿名内部类本质上是一个没有名字的子类对象、或者接口的实现类对象。
比如,先定义一个Animal抽象类,里面定义一个cry()方法,表示所有的动物有叫的行为,但是因为动物还不具体,cry()这个行为并不能具体化,所以写成抽象方法。
1 2 3 public abstract class Animal { public abstract void cry () ; }
接下来,我想要在不定义子类的情况下创建Animal的子类对象,就可以使用匿名内部类
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Test { public static void main (String[] args) { Animal a = new Animal (){ @Override public void cry () { System.out.println("猫喵喵喵的叫~~~" ); } } a.eat(); } }
需要注意的是,匿名内部类在编写代码时没有名字,编译后系统会为自动为匿名内部类生产字节码,字节码的名称会以外部类$1.class
的方法命名
匿名内部类的作用:简化了创建子类对象、实现类对象的书写格式。
匿名内部类的应用场景
学习完匿名内部类的基本使用之后,我们再来看一下匿名内部类在实际中的应用场景。其实一般我们会主动的使用匿名内部类。
只有在调用方法时,当方法的形参是一个接口或者抽象类,为了简化代码书写,而直接传递匿名内部类对象给方法。 这样就可以少写一个类。比如,看下面代码
1 2 3 public interface Swimming { public void swim () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Test { public static void main (String[] args) { Swimming s1 = new Swimming (){ public void swim () { System.out.println("狗刨飞快" ); } }; go(s1); Swimming s1 = new Swimming (){ public void swim () { System.out.println("猴子游泳也还行" ); } }; go(s1); } public static void go (Swimming s) { System.out.println("开始~~~~~~~~" ); s.swim(); System.out.println("结束~~~~~~~~" ); } }
匿名内部类的书写格式是什么样的? new类或接口(参数值…){ 类体(一般是方法重写); };
匿名内部类有啥特点?
匿名内部类本质就是一个子类,并会立即创建出一个子类对象
匿名内部类有啥作用、应用场景?
可以更方便的创建出一个子类对象。
匿名内部类通常作为一个参数传输给方法。
枚举 枚举是一种特殊的类,它的格式是:
1 2 3 public enum 枚举类名{ 枚举项1 ,枚举项2 ,枚举项3 ; }
枚举类的特点:1 2 3 public enum A {X, Y ,Z; }
1 2 3 4 5 6 7 8 Compiled from "A.java" public final class A extends java .lang.Enum<A> {public static final A X = new A ();public static final A Y= new A ();public static final A Z = new A ();public static A[] values();public static A valueof (java.lang.string) ;}
枚举类A是用class定义的,说明枚举确实是一个类,而且X,Y,Z都是A类的对象;而且每一个枚举项都是被public static final
修饰,所以被可以类名调用,而且不能更改。
枚举类的第一行只能罗列十些名称,这些名称都是常量,并且每个常量记住的都是枚举类的一个对象。
枚举类的构造器都是私有的(写不写都只能是私有的),因此,枚举类对外不能创建对象。
枚举都是最终类,不可以被继承。
枚举类中,从第二行开始,可以定义类的其他各种成员。
编译器为枚举类新增了几个方法,并且枚举类都是继承: java.lang.Enum类的,从enum类也会继承到一些方法。
用来表示一组信息,然后作为参数进行传输。 选择定义一个一个的常量来表示一组信息,并作为参数传输
参数值不受约束。 选择定义枚举表示一组信息,并作为参数传输
代码可读性好,参数值得到了约束,对使用老更友好,建议使用!
泛型 泛型 定义类、接口、方法时,同时声明了一个或者多个类型变量(如: ),称为泛型类、泛型接口,泛型方法、它们统称为泛型。
1 2 public class ArrayList <E>{}
当别人使用ArrayList集合创建对象时,new ArrayList<String>
就表示元素为String类型,new ArrayList<Integer>
表示元素为Integer类型。
作用:泛型提供了在编译阶段约束所能操作的数据类型,并自动进行检查的能力!这样可以避免强制类型转换及其可能出现的异常。
泛型类 修饰符class类名<类型变量,类型变量,.…>{ }
public class ArrayList{ }
注意:类型变量建议用大写的英文字母,常用的有:E、T、K、V等
泛型接口 1 2 3 4 public interface 接口名<类型变量>{ }
注意:类型变量建议用大写的英文字母,常用的有:E、T、K、V等
首先我们得有一个学生类和老师类
1 2 3 public class Teacher {}
1 2 3 public class Student { }
我们定义一个Data<T>
泛型接口,T表示接口中要处理数据的类型。
1 2 3 4 5 public interface Data <T>{ public void add (T t) ; public ArrayList<T> getByName (String name) ; }
接下来,我们写一个处理Teacher对象的接口实现类
1 2 3 4 5 6 7 8 9 10 11 public class TeacherData implements Data <Teacher>{ public void add (Teacher t) { } public ArrayList<Teacher> getByName (String name) { } }
接下来,我们写一个处理Student对象的接口实现类
1 2 3 4 5 6 7 8 9 10 11 public class StudentData implements Data <Student>{ public void add (Student t) { } public ArrayList<Student> getByName (String name) { } }
在实际工作中,一般也都是框架底层源代码把泛型接口写好,我们实现泛型接口就可以了。
泛型方法 1 2 3 public <泛型变量,泛型变量> 返回值类型 方法名(形参列表){ }
通配符 泛型限定的意思是对泛型的数据类型进行范围的限制。有如下的三种格式
> 表示任意类型
extends 数据类型> 表示指定类型或者指定类型的子类
super 数据类型> 表示指定类型或者指定类型的父类
泛型擦除 最后,关于泛型还有一个特点需要给同学们介绍一下,就是泛型擦除。什么意思呢?也就是说泛型只能编译阶段有效,一旦编译成字节码,字节码中是不包含泛型的 。而且泛型只支持引用数据类型,不支持基本数据类型。
泛型的注意事项。
泛型是工作在编译阶段的,一旦程序编译成class文件,class文件中就不存在泛型了,这就是泛型擦除。
泛型不支持基本数据类型,只能支持对象类型(引用数据类型)。
API(2) API(Application Programming interface)意思是应用程序编程接口,说人话就是Java帮我们写好的一些程序,如:类、方法等,我们直接拿过来用就可以解决一些问题。
Object类、toString、equals方法、对象克隆clone object类的作用: object类是Java中所有类的祖宗类,因此,Java中所有类的对象都可以直接使用Object类中提供的一些方法。
Object类的常见方法 方法名 说明 public String toString() 返回对象的字符串表示形式。 public boolean equals(Object o) 判断两个对象是否相等。 protected object clone() 对象克隆
1 2 3 public String toString () 调用toString()方法可以返回对象的字符串表示形式。 默认的格式是:“包名.类名@哈希值16 进制”
假设有一个学生类如下
1 2 3 4 5 6 7 8 9 public class Student { private String name; private int age; public Student (String name, int age) { this .name=name; this .age=age; } }
再定义一个测试类
1 2 3 4 5 6 public class Test { public static void main (String[] args) { Student s1 = new Student ("赵敏" ,23 ); System.out.println(s1.toString()); } }
如果,在Student类重写toString()方法,那么我们可以返回对象的属性值,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Student { private String name; private int age; public Student (String name, int age) { this .name=name; this .age=age; } @Override public String toString () { return "Student{name=‘" +name+"’, age=" +age+"}" ; } }
equals(Object o)方法
接下来,我们学习一下Object类的equals方法
1 2 public boolean equals (Object o) 判断此对象与参数对象是否"相等"
我们写一个测试类,测试一下
1 2 3 4 5 6 7 8 9 10 11 public class Test { public static void main (String[] args) { Student s1 = new Student ("赵薇" ,23 ); Student s2 = new Student ("赵薇" ,23 ); System.out.println(s1.equals(s2)); System.out.println(s1==s2); } }
但是如果我们在Student类中,把equals方法重写了,就按照对象的属性值进行比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class Student { private String name; private int age; public Student (String name, int age) { this .name=name; this .age=age; } @Override public String toString () { return "Student{name=‘" +name+"’, age=" +age+"}" ; } @Override public boolean equals (Object o) { if (this == o) return true ; if (o == null || getClass() != o.getClass()) return false ; Student student = (Student) o; if (age != student.age) return false ; return name != null ? name.equals(student.name) : student.name == null ; } }
总结一下Object的toString方法和equals方法
1 2 3 4 5 6 7 public String toString () 返回对象的字符串表示形式。默认的格式是:“包名.类名@哈希值16 进制” 【子类重写后,返回对象的属性值】 public boolean equals (Object o) 判断此对象与参数对象是否"相等" 。默认比较对象的地址值,和"==" 没有区别 【子类重写后,比较对象的属性值】
clone() 方法
接下来,我们学习Object类的clone()方法,克隆。意思就是某一个对象调用这个方法,这个方法会复制一个一模一样的新对象,并返回。
1 2 public Object clone () 克隆当前对象,返回一个新对象
想要调用clone()方法,必须让被克隆的类实现Cloneable接口。如我们准备克隆User类的对象,代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class User implements Cloneable { private String id; private String username; private String password; private double [] scores; public User () { } public User (String id, String username, String password, double [] scores) { this .id = id; this .username = username; this .password = password; this .scores = scores; } @Override protected Object clone () throws CloneNotSupportedException { return super .clone(); } }
接着,我们写一个测试类,克隆User类的对象。并观察打印的结果
1 2 3 4 5 6 7 8 9 10 11 public class Test { public static void main (String[] args) throws CloneNotSupportedException { User u1 = new User (1 ,"zhangsan" ,"wo666" ,new double []{99.0 ,99.5 }); User u2 = (User) u1.clone(); System.out.println(u2.getId()); System.out.println(u2.getUsername()); System.out.println(u2.getPassword()); System.out.println(u2.getScores()); } }
我们发现,克隆得到的对象u2它的属性值和原来u1对象的属性值是一样的。
上面演示的克隆方式,是一种浅克隆的方法,浅克隆的意思:拷贝出来的对象封装的数据与原对象封装的数据一模一样(引用类型拷贝的是地址值) 。
下面演示一下深拷贝User对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class User implements Cloneable { private String id; private String username; private String password; private double [] scores; public User () { } public User (String id, String username, String password, double [] scores) { this .id = id; this .username = username; this .password = password; this .scores = scores; } @Override protected Object clone () throws CloneNotSupportedException { User u = (User) super .clone(); u.scores = u.scores.clone(); return u; } }
Object中toString方法的作用是什么?存在的意义是什么?
基本作用:返回对象的字符串形式。
*存在的意义:让子类重写,以便返回子类对象的内容。
Object中equals方法的作用是什么?存在的意义是什么?
基本作用:默认是比较两个对象的地址是否相等。
存在的意义:让子类重写,以便用于比较对象的内容是否相同。
Object类提供的对象克隆方法 protected object clone() 对象克隆 当某个对象调用这个方法时,这个方法会复制一个一模一样的新对象返回。
浅克隆 拷贝出的新对象,与原对象中的数据一模一样(引用类型拷贝的只是地址)
深克隆 对象中基本类型的数据直接拷贝。 对象中的字符串数据拷贝的还是地址。 对象中还包含的其他对象,不会拷贝地址,会创建新对象。
Objects类 Objects是一个工具类,提供了很多操作对象的静态方法给我们使用。objects类的常见方法 方法名 说明 public static boolean equals(Object a,Object b) 先做非空判断,再比较两个对象 public static boolean isNull(Object obj) 判断对象是否为nul1,为null返回true ,反之 public static boolean nonNull(Object obj) 判断对象是否不为null,不为null则返回true,反之
下面写代码演示一下这几个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class Test { public static void main (String[] args) { String s1 = null ; String s2 = "itheima" ; System.out.println(s1.equals(s2)); System.out.println(Objects.equals(s1,s2)); System.out.println(Objects.isNull(s1)); System.out.println(s1==null ); System.out.println(Objects.nonNull(s2)); System.out.println(s2!=null ); } }
包装类
创建包装类对象
我们先来学习,创建包装类对象的方法,以及包装类的一个特性叫自动装箱和自动拆箱。我们以Integer为例,其他的可以自己学,都是类似的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Integer a = new Integer (10 );Integer b = Integer.valueOf(10 );Integer c = 10 ;int d = c;ArrayList<Integer> list = new ArrayList <>(); list.add(100 ); int e = list.get(0 );
包装类数据类型转换
在开发中,经常使用包装类对字符串和基本类型数据进行相互转换。
把字符串转换为数值型数据:包装类.parseXxx(字符串)
1 2 public static int parseInt (String s) 把字符串转换为基本数据类型
将数值型数据转换为字符串:包装类.valueOf(数据);
1 2 public static String valueOf (int a) 把基本类型数据转换为
1 2 3 4 5 6 7 8 9 10 11 12 13 String ageStr = "29" ;int age1 = Integer.parseInt(ageStr);String scoreStr = 3.14 ;double score = Double.prarseDouble(scoreStr);Integer a = 23 ;String s1 = Integer.toString(a);String s2 = a.toString();String s3 = a+"" ;String s4 = String.valueOf(a);
创建包装类的对象方式、自动装箱和拆箱的特性;
利用包装类提供的方法对字符串和基本类型数据进行相互转换
StringBuilder、StringBuffer、StringJoiner
StringBuilder方法演示
接下来我们用代码演示一下StringBuilder的用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Test { public static void main (String[] args) { StringBuilder sb = new StringBuilder ("itehima" ); sb.append(12 ); sb.append("黑马" ); sb.append(true ); sb.append(666 ).append("黑马2" ).append(666 ); System.out.println(sb); sb.reverse(); System.out.println(sb); System.out.println(sb.length()); String s = sb.toString(); System.out.println(s); } }
下面演示一下StringJoiner的基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Test { public static void main (String[] args) { StringJoiner s = new StringJoiner ("," ); s.add("java1" ); s.add("java2" ); s.add("java3" ); System.out.println(s); StringJoiner s1 = new StringJoiner ("," ,"[" ,"]" ); s1.add("java1" ); s1.add("java2" ); s1.add("java3" ); System.out.println(s1); } }
Math、System、Runtime
Math是数学的意思,该类提供了很多个进行数学运算的方法,如求绝对值,求最大值,四舍五入等,话不多说,直接上代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class MathTest { public static void main (String[] args) { System.out.println(Math.abs(-12 )); System.out.println(Math.abs(123 )); System.out.println(Math.abs(-3.14 )); System.out.println(Math.ceil(4.0000001 )); System.out.println(Math.ceil(4.0 )); System.out.println(Math.floor(4.999999 )); System.out.println(Math.floor(4.0 )); System.out.println(Math.round(3.4999 )); System.out.println(Math.round(3.50001 )); System.out.println(Math.max(10 , 20 )); System.out.println(Math.min(10 , 20 )); System.out.println(Math.pow(2 , 3 )); System.out.println(Math.pow(3 , 2 )); System.out.println(Math.random()); } }
接下来,学习一个System类,这是系统类,提供了一些获取获取系统数据的方法。比如获取系统时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class SystemTest { public static void main (String[] args) { System.exit(0 ); long time = System.currentTimeMillis(); System.out.println(time); for (int i = 0 ; i < 1000000 ; i++) { System.out.println("输出了:" + i); } long time2 = System.currentTimeMillis(); System.out.println((time2 - time) / 1000.0 + "s" ); } }
接下来,我们再学习一个Java的运行时类,叫Runtime类。这个类可以用来获取JVM的一些信息,也可以用这个类去执行其他的程序。话不多少,上代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class RuntimeTest { public static void main (String[] args) throws IOException, InterruptedException { Runtime r = Runtime.getRuntime(); System.out.println(r.availableProcessors()); System.out.println(r.totalMemory()/1024.0 /1024.0 + "MB" ); System.out.println(r.freeMemory()/1024.0 /1024.0 + "MB" ); Process p = r.exec("QQ" ); Thread.sleep(5000 ); p.destroy(); } }
BigDecimal
为了解决计算精度损失的问题,Java给我们提供了BigDecimal类,它提供了一些方法可以对数据进行四则运算,而且不丢失精度,同时还可以保留指定的小数位。下面看代码,演示一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public class Test2 { public static void main (String[] args) { double a = 0.1 ; double b = 0.2 ; BigDecimal a1 = BigDecimal.valueOf(a); BigDecimal b1 = BigDecimal.valueOf(b); BigDecimal c1 = a1.add(b1); System.out.println(c1); BigDecimal c2 = a1.subtract(b1); System.out.println(c2); BigDecimal c3 = a1.multiply(b1); System.out.println(c3); BigDecimal c4 = a1.divide(b1); System.out.println(c4); BigDecimal d1 = BigDecimal.valueOf(0.1 ); BigDecimal d2 = BigDecimal.valueOf(0.3 ); BigDecimal d3 = d1.divide(d2, 2 , RoundingMode.HALF_UP); System.out.println(d3); double db1 = d3.doubleValue(); double db2 = c1.doubleValue(); print(db1); print(db2); } public static void print (double a) { System.out.println(a); } }
BigDecimal的作用是什么? 解决浮点型运算时,出现结果失真的问题。
应该如何把浮点型转换成BigDecimal的对象? BigDecimal b1 = BigDecimal.valueOf(0.1)
Date对象记录的时间是用毫秒值来表示的。Java语言规定,1970年1月1日0时0分0秒认为是时间的起点,此时记作0,那么1000(1秒=1000毫秒)就表示1970年1月1日0时0分1秒,依次内推。
使用new Date(),这将创建一个代表当前时间的Date对象。 使用new Date(long millis),通过传递一个毫秒值来创建一个特定的Date对象。
调用Date对象的getTime()方法,它将返回Date对象表示的毫秒值。 使用System.currentTimeMillis(),直接获取当前时间的毫秒值,而无需创建Date对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Test1Date { public static void main (String[] args) { Date d = new Date (); System.out.println(d); long time = d.getTime(); System.out.println(time); time += 2 * 1000 ; Date d2 = new Date (time); System.out.println(d2); Date d3 = new Date (); d3.setTime(time); System.out.println(d3); } }
注意:创建SimpleDateFormat对象时,在构造方法的参数位置传递日期格式,而日期格式是由一些特定的字母拼接而来的。我们需要记住常用的几种日期/时间格式
1 2 3 4 5 6 7 8 9 10 11 12 字母 表示含义 yyyy 年 MM 月 dd 日 HH 时 mm 分 ss 秒 SSS 毫秒 "2022年12月12日" 的格式是 "yyyy年MM月dd日" "2022-12-12 12:12:12" 的格式是 "yyyy-MM-dd HH:mm:ss" 按照上面的格式可以任意拼接,但是字母不能写错
最后,上代码演示一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class Test2SimpleDateFormat { public static void main (String[] args) throws ParseException { Date d = new Date (); System.out.println(d); long time = d.getTime(); System.out.println(time); SimpleDateFormat sdf = new SimpleDateFormat ("yyyy年MM月dd日 HH:mm:ss EEE a" ); String rs = sdf.format(d); String rs2 = sdf.format(time); System.out.println(rs); System.out.println(rs2); System.out.println("----------------------------------------------" ); String dateStr = "2022-12-12 12:12:11" ; SimpleDateFormat sdf2 = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss" ); Date d2 = sdf2.parse(dateStr); System.out.println(d2); } }
日期格式化&解析案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class Test3 { public static void main (String[] args) throws ParseException { String start = "2023年11月11日 0:0:0" ; String end = "2023年11月11日 0:10:0" ; String xj = "2023年11月11日 0:01:18" ; String xp = "2023年11月11日 0:10:57" ; SimpleDateFormat sdf = new SimpleDateFormat ("yyyy年MM月dd日 HH:mm:ss" ); Date startDt = sdf.parse(start); Date endDt = sdf.parse(end); Date xjDt = sdf.parse(xj); Date xpDt = sdf.parse(xp); long startTime = startDt.getTime(); long endTime = endDt.getTime(); long xjTime = xjDt.getTime(); long xpTime = xpDt.getTime(); if (xjTime >= startTime && xjTime <= endTime){ System.out.println("小贾您秒杀成功了~~" ); }else { System.out.println("小贾您秒杀失败了~~" ); } if (xpTime >= startTime && xpTime <= endTime){ System.out.println("小皮您秒杀成功了~~" ); }else { System.out.println("小皮您秒杀失败了~~" ); } } }
Calendar
Calendar类表示日历,它提供了一些比Date类更好用的方法。
代表的是系统此刻时间对应的日历。
通过它可以单独获取、修改时间中的年、月、日、时、分、秒等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 public class Test4Calendar { public static void main (String[] args) { Calendar now = Calendar.getInstance(); System.out.println(now); int year = now.get(Calendar.YEAR); System.out.println(year); int days = now.get(Calendar.DAY_OF_YEAR); System.out.println(days); Date d = now.getTime(); System.out.println(d); long time = now.getTimeInMillis(); System.out.println(time); now.set(Calendar.MONTH, 9 ); now.set(Calendar.DAY_OF_YEAR, 125 ); System.out.println(now); now.add(Calendar.DAY_OF_YEAR, 100 ); now.add(Calendar.DAY_OF_YEAR, -10 ); now.add(Calendar.DAY_OF_MONTH, 6 ); now.add(Calendar.HOUR, 12 ); now.set(2026 , 11 , 22 ); System.out.println(now); } }
JDK8开始新增的日期、时间
为什么要学JDK8新增的时间?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Test { public static void main (String[] args) { Date d = new Date (); Calendar c = Calendar.getInstance(); int year = c.get(Calendar.YEAR); System.out.println(year); } }
LocalDate、LocalTime. LocalDateTime LocalDate:代表本地日期(年、月、日、星期) LocalTime:代表本地时间(时、分、秒、纳秒) LocalDateTime:代表本地日期、时间(年、月、日、星期、时、分、秒、纳秒)
它们获取对象的方案 方法名 示例 public static xxxx now(): 获取系统当前时间对应的该对象
LocalDate ld = LocalDate.now(); LocalTime lt = LocalTime.now(); LocalDateTime ldt = LocalDateTime.now();
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 public class Test1_LocalDate { public static void main (String[] args) { LocalDate ld = LocalDate.now(); System.out.println(ld); int year = ld.getYear(); int month = ld.getMonthValue(); int day = ld.getDayOfMonth(); int dayOfYear = ld.getDayOfYear(); int dayOfWeek = ld.getDayOfWeek().getValue(); System.out.println(year); System.out.println(day); System.out.println(dayOfWeek); LocalDate ld2 = ld.withYear(2099 ); LocalDate ld3 = ld.withMonth(12 ); System.out.println(ld2); System.out.println(ld3); System.out.println(ld); LocalDate ld4 = ld.plusYears(2 ); LocalDate ld5 = ld.plusMonths(2 ); LocalDate ld6 = ld.minusYears(2 ); LocalDate ld7 = ld.minusMonths(2 ); LocalDate ld8 = LocalDate.of(2099 , 12 , 12 ); LocalDate ld9 = LocalDate.of(2099 , 12 , 12 ); System.out.println(ld8.equals(ld9)); System.out.println(ld8.isAfter(ld)); System.out.println(ld8.isBefore(ld)); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 public class Test2_LocalTime { public static void main (String[] args) { LocalTime lt = LocalTime.now(); System.out.println(lt); int hour = lt.getHour(); int minute = lt.getMinute(); int second = lt.getSecond(); int nano = lt.getNano(); LocalTime lt3 = lt.withHour(10 ); LocalTime lt4 = lt.withMinute(10 ); LocalTime lt5 = lt.withSecond(10 ); LocalTime lt6 = lt.withNano(10 ); LocalTime lt7 = lt.plusHours(10 ); LocalTime lt8 = lt.plusMinutes(10 ); LocalTime lt9 = lt.plusSeconds(10 ); LocalTime lt10 = lt.plusNanos(10 ); LocalTime lt11 = lt.minusHours(10 ); LocalTime lt12 = lt.minusMinutes(10 ); LocalTime lt13 = lt.minusSeconds(10 ); LocalTime lt14 = lt.minusNanos(10 ); LocalTime lt15 = LocalTime.of(12 , 12 , 12 ); LocalTime lt16 = LocalTime.of(12 , 12 , 12 ); System.out.println(lt15.equals(lt16)); System.out.println(lt15.isAfter(lt)); System.out.println(lt15.isBefore(lt)); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public class Test3_LocalDateTime { public static void main (String[] args) { LocalDateTime ldt = LocalDateTime.now(); System.out.println(ldt); int year = ldt.getYear(); int month = ldt.getMonthValue(); int day = ldt.getDayOfMonth(); int dayOfYear = ldt.getDayOfYear(); int dayOfWeek = ldt.getDayOfWeek().getValue(); int hour = ldt.getHour(); int minute = ldt.getMinute(); int second = ldt.getSecond(); int nano = ldt.getNano(); LocalDateTime ldt2 = ldt.withYear(2029 ); LocalDateTime ldt3 = ldt.withMinute(59 ); LocalDateTime ldt4 = ldt.plusYears(2 ); LocalDateTime ldt5 = ldt.plusMinutes(3 ); LocalDateTime ldt6 = ldt.minusYears(2 ); LocalDateTime ldt7 = ldt.minusMinutes(3 ); LocalDateTime ldt8 = LocalDateTime.of(2029 , 12 , 12 , 12 , 12 , 12 , 1222 ); LocalDateTime ldt9 = LocalDateTime.of(2029 , 12 , 12 , 12 , 12 , 12 , 1222 ); System.out.println(ldt9.equals(ldt8)); System.out.println(ldt9.isAfter(ldt)); System.out.println(ldt9.isBefore(ldt)); LocalDate ld = ldt.toLocalDate(); LocalTime lt = ldt.toLocalTime(); LocalDateTime ldt10 = LocalDateTime.of(ld, lt); } }
zoneld、ZoneDateTime
我们学习代表时区的两个类。由于世界各个国家与地区的经度不同,各地区的时间也有所不同,因此会划分为不同的时区。每一个时区的时间也不太一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class Test4_ZoneId_ZonedDateTime { public static void main (String[] args) { ZoneId zoneId = ZoneId.systemDefault(); System.out.println(zoneId.getId()); System.out.println(zoneId); System.out.println(ZoneId.getAvailableZoneIds()); ZoneId zoneId1 = ZoneId.of("America/New_York" ); ZonedDateTime now = ZonedDateTime.now(zoneId1); System.out.println(now); ZonedDateTime now1 = ZonedDateTime.now(Clock.systemUTC()); System.out.println(now1); ZonedDateTime now2 = ZonedDateTime.now(); System.out.println(now2); } }
Instant
作用:可以用来记录代码的执行时间,或用于记录用户操作某个事件的时间点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class Test5_Instant { public static void main (String[] args) { Instant now = Instant.now(); long second = now.getEpochSecond(); System.out.println(second); int nano = now.getNano(); System.out.println(nano); System.out.println(now); Instant instant = now.plusNanos(111 ); Instant now1 = Instant.now(); Instant now2 = Instant.now(); LocalDateTime l = LocalDateTime.now(); } }
接下来,我们学习一个新增的日期格式化类,叫DateTimeFormater。它可以从来对日期进行格式化和解析。它代替了原来的SimpleDateFormat类。
接下来,将上面的方法用代码来演示一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class Test6_DateTimeFormatter { public static void main (String[] args) { DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss" ); LocalDateTime now = LocalDateTime.now(); System.out.println(now); String rs = formatter.format(now); System.out.println(rs); String rs2 = now.format(formatter); System.out.println(rs2); String dateStr = "2029年12月12日 12:12:11" ; LocalDateTime ldt = LocalDateTime.parse(dateStr, formatter); System.out.println(ldt); } }
Duration、Period
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Test7_Period { public static void main (String[] args) { LocalDate start = LocalDate.of(2029 , 8 , 10 ); LocalDate end = LocalDate.of(2029 , 12 , 15 ); Period period = Period.between(start, end); System.out.println(period.getYears()); System.out.println(period.getMonths()); System.out.println(period.getDays()); } }
接下来,我们学习Duration类。它是用来表示两个时间对象的时间间隔。可以用于计算两个时间对象相差的天数、小时数、分数、秒数、纳秒数;支持LocalTime、LocalDateTime、Instant等时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Test8_Duration { public static void main (String[] args) { LocalDateTime start = LocalDateTime.of(2025 , 11 , 11 , 11 , 10 , 10 ); LocalDateTime end = LocalDateTime.of(2025 , 11 , 11 , 11 , 11 , 11 ); Duration duration = Duration.between(start, end); System.out.println(duration.toDays()); System.out.println(duration.toHours()); System.out.println(duration.toMinutes()); System.out.println(duration.toSeconds()); System.out.println(duration.toMillis()); System.out.println(duration.toNanos()); } }
Period有啥作用?
可以用于计算两个LocalDate对象相差的年数、月数、天数。
Duration有啥作用?
可以用于计算两个时间对象相差的天数、小时数、分数、秒数、纳秒数,支持 LocalTime.LocalDateTime.lnstant等时间。
Arrays 我们先认识一下Arrays是干什么用的,Arrays是操作数组的工具类,它可以很方便的对数组中的元素进行遍历、拷贝、排序等操作 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class ArraysTest1 { public static void main (String[] args) { int [] arr = {10 , 20 , 30 , 40 , 50 , 60 }; System.out.println(Arrays.toString(arr)); int [] arr2 = Arrays.copyOfRange(arr, 1 , 4 ); System.out.println(Arrays.toString(arr2)); int [] arr3 = Arrays.copyOf(arr, 10 ); System.out.println(Arrays.toString(arr3)); double [] prices = {99.8 , 128 , 100 }; Arrays.setAll(prices, new IntToDoubleFunction () { @Override public double applyAsDouble (int value) { return prices[value] * 0.8 ; } }); System.out.println(Arrays.toString(prices)); Arrays.sort(prices); System.out.println(Arrays.toString(prices)); } }
自定义排序规则Comparable Arrays根本就不知道按照什么规则进行排序。为了让Arrays知道按照什么规则排序,我们有如下的两种办法。
排序方式1: 让Student类实现Comparable接口,同时重写compareTo方法。Arrays的sort方法底层会根据compareTo方法的返回值是正数、负数、还是0来确定谁大、谁小、谁相等。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class Student implements Comparable <Student>{ private String name; private double height; private int age; @Override public int compareTo (Student o) { return this .age - o.age; } @Override public String toString () { return "Student{" + "name='" + name + '\'' + ", height=" + height + ", age=" + age + '}' ; } }
排序方式2: 在调用Arrays.sort(数组,Comparator比较器);
时,除了传递数组之外,传递一个Comparator比较器对象。Arrays的sort方法底层会根据Comparator比较器对象的compare方法方法的返回值是正数、负数、还是0来确定谁大、谁小、谁相等。代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class ArraysTest2 { public static void main (String[] args) { Student[] students = new Student [4 ]; students[0 ] = new Student ("蜘蛛精" , 169.5 , 23 ); students[1 ] = new Student ("紫霞" , 163.8 , 26 ); students[2 ] = new Student ("紫霞" , 163.8 , 26 ); students[3 ] = new Student ("至尊宝" , 167.5 , 24 ); Arrays.sort(students, new Comparator <Student>() { @Override public int compare (Student o1, Student o2) { return Double.compare(o1.getHeight(), o2.getHeight()); } }); System.out.println(Arrays.toString(students)); } }
如果认为左边对象大于右边对象应该返回正整数
如果认为左边对象小于右边对象应该返回负整数
如果认为左边对象等于右边对象应该返回0整数
Lambda表达式 Lambda表达式
Lambda表达式是JDK8开始新增的一种语法形式;作用:用于简化匿名内部类代码的书写。
格式1 2 3 (被重写方法的形参列表) -> { 被重写方法的方法体代码; }
需要给说明一下的是,在使用Lambda表达式之前,必须先有一个接口,而且接口中只能有一个抽象方法。(注意:不能是抽象类,只能是接口)
注意:Lambda表达式只能简化函数式接口的匿名内部类!!!
有且仅有一个抽象方法的接口。
注意:将来我们见到的大部分函数式接口,上面都可能会有一个@Functionalnterface的注解,有该注解的接就必定是函数式接口
Lambda表达式的省略写法 (进一步简化Lambda表达式的写法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 1. Lambda的标准格式 (参数类型1 参数名1 , 参数类型2 参数名2 )->{ ...方法体的代码... return 返回值; } 2. 在标准格式的基础上()中的参数类型可以直接省略 (参数名1 , 参数名2 )->{ ...方法体的代码... return 返回值; } 3. 如果{}总的语句只有一条语句,则{}可以省略、return 关键字、以及最后的“;”都可以省略 (参数名1 , 参数名2 )-> 结果 4. 如果()里面只有一个参数,则()可以省略 (参数名)->结果
参数类型可以省略不写。
如果只有一个参数,参数类型可以省略,同时()也可以省略。
如果Lambda表达式中的方法体代码只有一行代码,可以省略大括号不写,同时要省略分号!此时,如果这行代码是return语句,也必须去掉return不写。
方法引用 静态方法的引用
使用场景 如果某个Lambda表达式里只是调用一个静态方法,并且前后参数的形式一致,就可以使用静态方法引用。
我们先学习静态方法的引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Test1 { public static void main (String[] args) { Student[] students = new Student [4 ]; students[0 ] = new Student ("蜘蛛精" , 169.5 , 23 ); students[1 ] = new Student ("紫霞" , 163.8 , 26 ); students[2 ] = new Student ("紫霞" , 163.8 , 26 ); students[3 ] = new Student ("至尊宝" , 167.5 , 24 ); Arrays.sort(students, new Comparator <Student>() { @Override public int compare (Student o1, Student o2) { return o1.getAge() - o2.getAge(); } }); Arrays.sort(students, (o1, o2) -> o1.getAge() - o2.getAge()); } }
现在,我想要把下图中Lambda表达式的方法体,用一个静态方法代替
准备另外一个类CompareByData类,用于封装Lambda表达式的方法体代码;
1 2 3 4 5 public class CompareByData { public static int compareByAge (Student o1, Student o2) { return o1.getAge() - o2.getAge(); } }
现在我们就可以把Lambda表达式的方法体代码,改为下面的样子
1 Arrays.sort(students, (o1, o2) -> CompareByData.compareByAge(o1, o2));
Java为了简化上面Lambda表达式的写法,利用方法引用可以改进为下面的样子。实际上就是用类名调用方法,但是把参数给省略了。 这就是静态方法引用
1 2 Arrays.sort(students, CompareByData::compareByAge);
实例方法的引用
使用场景 如果某个Lambda表达式里只是调用一个实例方法,并且前后参数的形式一致,就可以使用实例方法引用。
还是基于上面的案例,我们现在来学习一下实例方法的引用。现在,我想要把下图中Lambda表达式的方法体,用一个实例方法代替。
1 2 CompareByData compare = new CompareByData ();Arrays.sort(students, (o1, o2) -> compare.compareByAgeDesc(o1, o2));
最后,再将Lambda表达式的方法体,直接改成方法引用写法。实际上就是用类名调用方法,但是省略的参数 。这就是实例方法引用
1 2 CompareByData compare = new CompareByData ();Arrays.sort(students, compare::compareByAgeDesc);
特定类型方法的引用 1 2 3 4 Java约定: 如果某个Lambda表达式里只是调用一个实例方法,并且前面参数列表中的第一个参数作为方法的主调, 后面的所有参数都是作为该实例方法的入参时,则就可以使用特定类型的方法引用。 格式: 类型::方法名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Test2 { public static void main (String[] args) { String[] names = {"boby" , "angela" , "Andy" ,"dlei" , "caocao" , "Babo" , "jack" , "Cici" }; Arrays.sort(names, new Comparator <String>() { @Override public int compare (String o1, String o2) { return o1.compareToIgnoreCase(o2); } }); Arrays.sort(names, ( o1, o2) -> o1.compareToIgnoreCase(o2) ); Arrays.sort(names, String::compareToIgnoreCase); System.out.println(Arrays.toString(names)); } }
构造器引用
使用场景 如果某个Lambda表达式里只是在创建对象,并且前后参数情况一致,就可以使用构造器引用。
我们学习最后一种方法引用的形式,叫做构造器引用。还是先说明一下,构造器引用在实际开发中应用的并不多,目前还没有找到构造器的应用场景。所以大家在学习的时候,也只是关注语法就可以了。
现在,我们准备一个JavaBean类,Car类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class Car { private String name; private double price; public Car () { } public Car (String name, double price) { this .name = name; this .price = price; } public String getName () { return name; } public void setName (String name) { this .name = name; } public double getPrice () { return price; } public void setPrice (double price) { this .price = price; } @Override public String toString () { return "Car{" + "name='" + name + '\'' + ", price=" + price + '}' ; } }
因为方法引用是基于Lamdba表达式简化的,所以也要按照Lamdba表达式的使用前提来用,需要一个函数式接口,接口中代码的返回值类型是Car类型
1 2 3 interface CreateCar { Car create (String name, double price) ; }
最后,再准备一个测试类,在测试类中创建CreateCar接口的实现类对象,先用匿名内部类创建、再用Lambda表达式创建,最后改用方法引用创建。同学们只关注格式就可以,不要去想为什么(语法就是这么设计的)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class Test3 { public static void main (String[] args) { CreateCar cc1 = new CreateCar (){ @Override public Car create (String name, double price) { return new Car (name, price); } }; CreateCar cc2 = (name, price) -> new Car (name, price); CreateCar cc3 = Car::new ; Car car = cc3.create("奔驰" , 49.9 ); System.out.println(car); } }