- 一个源文件只能有一个public类,其他类的个数不限。编译后,每一个类都对应生成一个 .class 文件
- 源文件名与public类同名
- javadoc注释
- 文档注释用终端生成html文件命令:
javadoc -d 文件夹地址 javadoc标签 XXX.java
- Java程序中 "+" 号的使用
- 当左右两边都是数值型时,做加法运算
- 当左右两边有一方为字符串,则做拼接运算
System.out.println(100+98); //198
System.out.println("100"+98); //10098
System.out.println(100+"Hello"); //100Hello
System.out.println("Hello"+100+98); //Hello10098
数据类型
引用类型
- 类
- 接口
- 数组
- Java默认浮点数是double类型,float类型要在数值后加 f/F
- Java浮点类型保留小数点后位数
- float - 保留小数点后7位 - C语言中保留6位
- double - 保留小数点后16位 - C语言中保留15位
double a = 0.92381930109121314334452222222; System.out.println(a); //0.9238193010912131 float b = 0.213123123123f; System.out.println(b); //0.21312313
//一些小细节
double a = 2.7;
double b = 8.1 / 3; //实际结果为2.7
System.out.println(a); //2.7
System.out.println(b); //2.6999999999999997
if(a == b){
System.out.println("相等");
}else{
System.out.println("不相等"); //输出不相等
}
//由此我们可以得到一个重要结论
//当对两个运算结果为小数的进行判断比较是,应当小心
//应该用两个小数的差值的绝对值小于某个很小的范围时,认为两个运算结果相等
//应按照如下写法
if(Math.abs(a - b) < 0.0001){
System.out.println("相等"); //输出相等
}
- 字符和编码时一一对应的
- Java中不可以用0和非0来替代false和true,这点与C语言不同。
- Boolean只能是true或者false 不能是0或者1
- 低精度向高精度自动转换
- byte==>short==>int==>long==>float==>double
- char==>int==>long==>float==>double
//细节 int n1 = 10; float d1 = n1 + 1.1; //错误,右边其实结果是double类型 float d1 = n1 + 1.1F; //正确写法 System.out.println(d1); int n2 = 1.0; //错误 右边是double 高精度不能自动转换为低精度 //byte和short、char之间不发生自动类型转换 //byte和int自动类型转换的一些小细节 byte b1 = 10; //可以,具体数复制给byte时,先判断数值范围,再判断类型 10在byte范围内:-128-127 int a = 1; b1 = a; //变量a在内存中占四个字节,是不能把他赋给byte,因为byte只有一个字节的空间 char c1 = 'a'; b1 = c1; //错误,byte和char之间不能自动类型转换 //byte、short、char之间可以计算,在计算时首先转换为int类型 byte b2 = 1; byte b3 = 1; short s1 = 1; short s2 = s1 + b2; // 错误,b2 + s1 在计算时转换为了int型 int b = s1 + b2; //正确 byte b4 = b2 + b3; //错误,b2 + b3 ==> int //boolean 不参与类型的自动转换 //自动提升原则:表达式结果的类型自动提升为操作数中最大的类型 byte b5 = 1; short s4 = 100; int c = 1; double num = 1.1; float f1 = 1.1f; double num1 = b5 + s4 + c + num; //必须用double类型的变量接收右边的运算结果,因为右边运算结果的类型以及提升到double类型 double num2 = b5 + s4 + c + f1; //这样可以,右边运算结果为float,满足自动转换原则强制类型转换
- 高精度类型向低精度类型转换,由编写代码的人使用强制转换符号**()**进行类型转换,同时转换过程中会造成精度损失
- C语言中直接通过赋值号就能进行类型转换,但是也会造成精度损失,编译可以通过,但是编译器会提示警告。C语言也可以用强制类型转换符 ( 进行有精度损失的强制类型转换)
- Java中高精度类型向低精度类型转换必须使用强制转换符号(),会造成精度损失,否则编译无法通过,直接提示错误。
- 强制类型转换符只对最近的操作数有效,这点与C语言相同,C语言中是因为**()**运算符优先级高。
int a = (int)10+ 2.1*2; //错误,右边其实还是double型 //正确写法 int a = (int)(10+ 2.1*2);
- char类型可以保存 int 类型的常量值,但是不能保存 int 类型的变量值(需要强制转换)。这点与C语言不同
- C语言中** 只要被char保存的值在其范围内,就可以保存,无论变量值还是常量值**;这点原因还是因为C语言中直接通过赋值号就能进行类型转换
- byte进行运算时,当作int类型处理,运算结果用int接收
- Short进行运算时,当作int类型处理,运算结果用int接收
- char进行运算时,当作int类型处理,运算结果用int接收
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-881UZUUn-1652873405290)(https://cdn.jsdelivr.net/gh/MCXXHYK/PicGo/202205121717674.png#crop=0&crop=0&crop=1&crop=1&id=HDf9U&originHeight=896&originWidth=1356&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
基本数据类型和字符串的转换1.** 基本数据类型转字符串**:加 “”
int n1 = 100; float f1 = 4.5F; double d1 = 1.2; boolean b1 = true; String s1 = n1 + ""; String s2 = f1 + ""; String s3= d1 + ""; String s4 = b1 + ""; System.out.println(s1 + " " + s2 + " " + s3 + " " + s4); //100 4.5 1.2 true 字符串
-
**字符串转基本数据类型 **
- 语法:通过基本数据类型的包装类调用parseXXX方法即可
int a =Integer.parseInt("123");
double b = Double.parseDouble("123");
float f = Float.parseFloat("123");
short s = Short.parseShort("12");
long l = Long.parseLong("2131");
boolean b = Boolean.parseBoolean("true");
byte by = Byte.parseByte("11");
//字符串==>取出字符
String s1 = "123";
char c1 = s1.charAt(0); //1
char c2 = s1.charAt(1); //2
char c3 = s1.charAt(2); //3
关系运算符 instanceof:检查是否是类的对象,返回值为true和false
boolean judge = "hh" instanceof String; //true短路与 && 、逻辑与 & 区别
- 短路与有一个条件为假,后面条件直接不用判断,且后面代码不再执行,结果返回假;
- 逻辑与必须要把所有条件都判断完,才会返回结果 效率低
- 短路或有一个条件为真,后面条件直接不用判断,且后面代码不再执行,结果返回真;
- 逻辑或必须要把所有条件都判断完,才会返回结果 效率低
tips:**复合赋值运算符再进行计算时会进行类型转换 **
byte b1 = 123; b1 += 1; //等价于 b1 = (byte)(b1 + 1); //分析:按理说 b1 += 1;右边是 b1 +1 ==>int 赋值给左边byte会发生错误, //但是这里其实复合赋值运算符进行运算是会自动进行类型转换的 //b1++也是如此 //b1++等价于 b1 = (byte)(b1+1); 也会发生类型转换 //细节 int i = 1; i = i++; //1) temp = i ; 2) i = i +1; 3) i = temp; 执行顺序是这样的 System.out.println(i); //1 int j = 0; j = i++; //1) temp = i ; 2) i = i +1; 3) j = temp; 执行顺序是这样的 j=1 int a = 1; a = ++a; System.out.println(a); //2 执行顺序 1) a = a +1; 2) temp = a ; 3) a = temp;键盘输入
import java.util.Scanner; //导包
public class UserScanner {
public static void main(String[] args){
//1.创建对象
Scanner sc = new Scanner(System.in);
//2.调用方法
String s1 = sc.next(); //字符串
int n1 = sc.nextInt(); //整型值
double d1 = sc.nextDouble();//浮点型输入
char c1 = sc.next().charAt(0); // 字符型输入
System.out.println("==================");
System.out.println(s1);
System.out.println(n1);
System.out.println(d1);
System.out.println(c1);
// System.out.println(s1);
}
}
金字塔
public class Pyramid {
void disPlayPyramid () {
int totol = 5;
//i控制层数
for (int i = 1 ; i <= totol ; i++) {
//每次打印star之前先打印空格
for (int k = 1; k <= totol-i; k++){
System.out.print(" ");
}
//j控制每一层打印的star个数
for (int j = 1;j <= 2*i-1;j++){
if(j == 1 || j == 2*i-1 || i == totol){
System.out.print("*");
}else{
System.out.print(" ");
}
}
System.out.println("");
}
}
public static void main(String[] args) {
Pyramid py = new Pyramid();
py.disPlayPyramid();
}
}
原码、反码、补码
规则:
- 最高位为符号位,0 ==> 正数 ,1 ==> 负数
- 正数 ==> 原码 = 反码 =补码
- 0的原码 = 反码 =补码 = 0
- 负数的原码 符号位不变,其余按位取反 -----> 反码 加一----> 补码 减一------> 反码 取反 ------>得原码的绝对值
- 补码转原码时,若其符号为 1 时,说明其是负数,所转的原码是该该负数的绝对值
- 计算机运算(存储)时都是以补码形式运算的
- 看运算结果时,要看的是其原码(运算结果是补码时,要转成原码才能打印出来)
- **按位与&、按位或|、按位异或 ^、按位取反 ~ **
- 按位与&:两个数全为1,结果为1 ;否则取0 ;
- 按位或|:两个数全为0,结果为0 ;否则为1;
- 按位异或 ^:两个数不同时为1(1 0);相同时为0;(0 0 、1 1);
- 按位取反 ~:0 ==> 1 ; 1 ==> 0 ;
//2 0010 3 0011 2 & 3 = 0010 ==> 2 //-2 原码:10000000 00000000 00000000 00000010 // 补码:11111111 11111111 11111111 11111110 // ~-2 00000000 00000000 00000000 00000001 这是补码,运算结果要转成原码 // 运算结果的原码:00000000 00000000 00000000 00000001 // ~-2 ==> 1
- **算数右移 >> 、算数左移 << 、逻辑右移 >>> **
- 算数右移 >>: 低位溢出,符号位不变,并用符号位补溢出的高位 a >> b ===>本质:a / 2/ 2/……/2 除以b个2
- 算数左移 <<:符号位不变,低位补0 a << b ====>本质:a _ 2 _ 2 _ ……_2 乘以b个2
- 逻辑右移 >>>:低位溢出,高位补0 ;逻辑右移 >>>也叫无符号右移
Double d1 = Math.random(); // 返回值时介于(0.00,1.00)的Double值 //随机生成1-100之间的随机整数 int n1 = (int)(Math.random()*100)+1 //(int)(Math.random()*100) 0-99循环控制 break
- break出现在多层循环中,可以通过指定循环标签,来终止哪一层循环,但是实际开发中尽量避免使用标签;break label1;
- 如果break在多层循环中,没有指定标签,则break终止的是离他最近的一层循环
- 字符串比较 String1.equal(String2);
- 把String1写成被计较的值,可以避免控制异常
//实现登录验证,有三次机会,如果用户名为”胡永康“,密码为”123456“,则提示登录成功;
//否则提示登录失败,并显示还有几次机会
void loginValidate(){
int n = 3;
Scanner sc = new Scanner(System.in);
for( int i = 1; i <=n; i++){
System.out.print("请输入用户名:");
String uname = sc.next();
System.out.println("");
System.out.print("请输入密码:");
String password = sc.next();
if("胡永康".equals(uname) && "123456".equals(password)){
System.out.println("登录成功....");
break;
}else{
System.out.println("登录失败,还剩"+(n-i)+"次机会!");
}
}
}
continue
- continue出现在多层循环中,可以通过指定循环标签,来直接进行哪一层的下一次循环,但是实际开发中尽量避免使用标签;continue label1;
- 如果continue在多层循环中,没有指定标签,则continue直接进行的是离他最近的一层循环的下一次循环
- return 可以返回方法的返回值,当在方法中直接使用 **return;**时,则该方法直接结束,后面不再执行;
- return 用在主方法main上时,表示程序直接终止
//1.动态初始化
int[] a = new int[5]; //数组动态初始化
int a[] = new int[5]; //两种写法均可
//也可以先声明再初始化
int[] a; // 先只声明,而不分配空间
a = new int[5]; //此时分配空间
//2.静态初始化
int a[] = {2,5,6,2,5,7,9};
数组使用注意事项
- 数组创建后没有赋值,有默认值
int byte short long ==> 0 float ==> 0.0 double ==> 0.0 char ==> /u0000 string ==> null boolean ==> false
- 数组下标必须在指定范围内使用
- 数组属于引用类型,数组型数据是对象
- 创建新数组,
- 使新数组的长度等于原来的数组加上要扩容的长度,
- 把原来的数组内容拷贝过来,
- 把新内容填入新的数组,
- 并让原来的数组名指向新的数组。
//扩容arr数组
int[] arr = {1,2,3};
//1.创建新的数组
int[] arrNew = new int[arr.length + 1]; //给新的数组创建原来的数组长度加一的空间
//2.遍历将原来的数组内容依次拷贝过来
for( int i = 0; i < arr.length; i++){
arrNew[i] = arr[i];
}
//3.把新内容填入新的数组
arrNew[arr.length] = 4;
//4.让原来的数组名指向新的数组
arr = arrNew; //arrNew[]自动被销毁(垃圾回收机制)
优化版本
import java.util.Scanner;
public class ArrayAdd {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
//原来的数组
int[] arr = {1,2,3};
do {
//1.创建新数组
int arrNew[] = new int[arr.length+1];
//2.拷贝原来数组内容到新数组
for( int i = 0; i < arr.length; i++) {
arrNew[i] = arr[i];
}
//3.询问用户是否添加新内容到数组
System.out.println("是否添加新内容到数组?(Y/N)");
char input = sc.next().charAt(0);
if( input == 'N') {
break;
}else if ( input == 'Y') {
System.out.println("请输入你想添加的数值:");
//3.获取要添加的数值
Integer addNum = sc.nextInt();
arrNew[arr.length] = addNum;
//4.将原来的的数组名指向新的数组
arr = arrNew;
System.out.println("添加成功!===========");
//5.打印数组中当前数据
for ( int i = 0; i < arr.length; i++ ) {
System.out.print("arr["+i+"]==>"+arr[i]+"t");
}
}else{
System.out.println("添加失败!");
break;
}
}while(true);
}
}
数组缩减
import java.util.Scanner;
public class ArrayReduce {
public static void main(String[] args) {
//1.定义一个数组
int[] arr = {1,2,3};
//2.创建输入环境
Scanner sc = new Scanner(System.in);
//3.进入数组缩减
do {
//4.定义一个新的数组
int[] arrNew = new int[arr.length - 1];
System.out.println("是否要缩减数组?(y/n)");
char input = sc.next().charAt(0);
if( input == 'n'){
break;
}else if ( input == 'y' && arr.length > 1) {
//5.拷贝前length-1个元素到新数组
for ( int i = 0; i < arrNew.length; i++) {
arrNew[i] = arr[i];
}
//6.原来数组指向新的数组
arr = arrNew;
//7.打印缩减后的数组
System.out.println("缩减后的数组==================");
for ( int j = 0; j < arr.length ; j++) {
System.out.print(arr[j]+"t");
}
}else if ( arr.length <= 1 ){
System.out.println("不能再缩减了!");
break;
}
}while(true);
}
}
排序
1.冒泡排序
算法思想:
//将其:57,12,63,7,4,9,12,52使用冒泡排序按照从小到大的顺序排序
public class BubbleSort {
public static void main(String[] args){
//1.定义一个数组存放数据,静态初始化数组
int[] arr = {57,12,63,7,4,9,12,52};
//2.冒泡排序 arr.length -1 趟排序 每趟排序确定一个最大值 i ==> 趟数 ==> 已排好序的数个数
for (int i = 0 ; i < arr.length - 1 ; i++ ) {
int flag = 1;
if( arr.length == 0) {
System.out.println("排序失败,数组长度为0!");
break;
}
//3.一趟冒泡排序 只给前面未被排序的元素排序 后面 i 个元素已经排好序 <==> int j = 0; j < arr.length - 1 - i
for( int j = 0; j < arr.length - 1 - i; j++) {
//比较相邻元素大小,若不是按照递增顺序,则发生交换
if( arr[j] > arr[j + 1]){
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
flag = -1;
}
}
//如果一趟排序之后flag没变化,说明数组已经有序,无需在进行下次排序,直接跳出循环
if(flag == 1){
break;
}else{
System.out.println("n第"+(i+1)+"趟排序后的数组===========");
for ( int k = 0; k < arr.length; k++ ) {
System.out.print(arr[k]+"t");
}
}
}
//4.打印排序后的数组
System.out.println("n全部排序完成后的数组===========");
for ( int k = 0; k < arr.length; k++ ) {
System.out.print(arr[k]+"t");
}
}
}
总结:
- 一共发生arr.length - 1 趟排序,
- 每趟排序确定一个最大值,
- 通过相邻元素比较进行交换,
- 每趟排序发生 arr.length - 1 - i 次比较
- 每个元素是一个一维数组的数组
- 如果要放二维数组的第 i 个元素中第 j 个值,应该这样访问: arr[i-1][j-1]; 因为数组下标都是从0开始的
public static void main(String[] args){
int[][] arr = {{1, 2, 3, 4, 5}, {0, 0, 1, 2, 3}, {1, 2, 5, 3, 7}, {4, 5, 6, 2, 1}, {0, 0, 0, 0, 0}};
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("");
}
}
二维数组的初始化
动态初始化01
public static void main(String[] args){
// int[][] arr = new int[i][j]; i 代表这个数组初始化为含有 i 个一维数组的数组,j代表每个一维数组含有j个元素
int[][] arr = new int[5][4]; //5 代表这个数组初始化为含有5个一维数组的数组,4代表每个一维数组含有4个元素
}
动态初始化02(列数不确定)
public static void main(String[] args){
// int[][] arr = new int[i][]; i 代表这个数组初始化为含有 i 个一维数组的数组,但是每个一维数组还没有开辟空间
int[][] arr = new int[3][]; //3 代表这个数组初始化为含有5个一维数组的数组,但是每个一维数组还没有开辟空间
for( int i = 0; i < arr.length; i++) {
//给二维数组中的每一个一维数组开辟内存空间;如果不给每个一维数组分配空间,那么每个一维数组都为 null,即arr[i] == null
arr[i] = new int[i + 1];
//给一维数组遍历赋值
for( int j = 0; j < arr[i].length; j++) {
arr[i][j] = i+1;
}
}
System.out.println("====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("");
}
}
杨辉三角形
public class YangHui {
public static void main(String[] args) {
//动态创建一个二维数组
int[][] arr = new int[10][];
//遍历二维数组
for( int i = 0; i < arr.length; i++) {
//给每一个一维数组开辟内存空间
arr[i] = new int[i + 1];
//遍历一维数组
for( int j = 0; j < arr[i].length; j++) {
if( j == 0 || j == arr[i].length - 1) {
arr[i][j] = 1;
}else{
arr[i][j] = arr[i - 1][j] + arr[i - 1][j - 1];
}
}
}
// 打印二维数组
for( int i = 0; i < arr.length; i++) {
for( int j = 0; j < arr[i].length; j++) {
System.out.print(arr[i][j]+"t");
}
System.out.println("");
}
}
}
类与对象(基础)
基本介绍
- 对象:属性 + 行为;是一个具体的实例。
- 类:一种数据类型
- 类的五大成员:属性、方法、构造器、代码块、内部类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u1Hkpi4B-1652873405294)(https://raw.githubusercontent.com/MCXXHYK/PicGo/main/image-20220503200153147.png#crop=0&crop=0&crop=1&crop=1&id=EVclS&originHeight=1086&originWidth=2484&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pHktv11U-1652873405297)(https://raw.githubusercontent.com/MCXXHYK/PicGo/main/image-20220503200311717.png#crop=0&crop=0&crop=1&crop=1&id=QwwBU&originHeight=1058&originWidth=2458&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
- Java内存分配机制:
- 栈内存:一般存放基本数据类型(局部变量)
- 堆内存:存放对象、数组等
- 方法区:常量池(常量,例如字符串)、类加载信息
- 一个方法只能有一个返回值,如何返回多个返回值?可用数组解决
//一个方法只能有一个返回值,如何返回多个返回值?可用数组解决
class AA {
public int[] getSumAndSub(int num1, int num2) {
int[] resArr = new int[2];
resArr[0] = num1 + num2;
resArr[1] = num1 - num2;
//用数组返回多个返回值
return resArr;
}
}
- 方法调用细节
- 同一个类中调用类中的方法,直接调用即可;
- 跨类调用方法时,需要创建该方法类的对象,用对象调用该方法。例如:A类中调用B类方法,需要先创建B类的对象,再用B类的对象调用B类的方法。
- 跨类的方法调用与方法的访问修饰符有关
- 成员方法传参细节
- 基本数据类型传参,是值拷贝,不可以通过形参修改实参
- 引用类型传参,传递的是地址,可以通过形参修改实参
递归必须向退出递归的条件逼近,否则就会无限递归,造成栈溢出。
迷宫问题
public class MiGong {
//初始化一个二维数组
static int[][] initTwoDemensionArray( int i, int j) {
//1.开辟二维数组的内存空间
int[][] arr = new int[i][j];
//2.初始化数组
for( int k = 0; k < i; k++) {
for( int t = 0; t < j; t++) {
if(k > 0 && t >0 && k < i-1 && t < j-1) {
arr[k][t] = 0;
}else{
arr[k][t] = 1;
}
}
arr[3][1] = 1;
arr[3][2] = 1;
}
return arr;
}
//打印二维数组
static void printTwoDemensionArray(int[][] arr) {
for( int i = 0; i < arr.length; i++) {
for( int j = 0; j
System.out.print(arr[i][j]+" ");
}
System.out.println();
}
}
//寻找路径 策略:下 ==> 右 ==> 上 ==> 左
boolean findWay(int[][] arr, int i, int j) {
//判断是否到达终点 避坑:这里到达终点条件不要写成 i == 4 && j == 5 ,因为这样写的话arr[4][5]就不会插旗
if( arr[4][5] == 2) {
return true;
}else {
//1.判断当前位置状态( 0, 1, 2, 3 )
// 0代表此位置可以走,但是没有插旗,将其划定为路径
if (arr[i][j] == 0) {
//插旗 假定可以周围可以走通
arr[i][j] = 2;
//探路 下 ==> 右 ==> 上 ==> 左
if (findWay(arr, i + 1, j)) { //向下走
return true;
} else if (findWay(arr, i, j + 1)) { //向右走
return true;
} else if (findWay(arr, i - 1, j)) { //向上走
return true;
} else if (findWay(arr, i, j - 1)) { //向左走
return true;
} else {
arr[i][j] = 3;
return false;
}
} else { //当前位置为 1 / 2 / 3
return false;
}
}
}
public static void main(String[] args) {
//1.初始化迷宫
int[][] arr = initTwoDemensionArray(6,7);
//2.打印迷宫
printTwoDemensionArray(arr);
//3.从(1,1)出发找寻路径,终点为(4,5)
MiGong m1 = new MiGong();
m1.findWay(arr, 1, 1);
//4.打印插好旗的迷宫 (迷宫路径)
System.out.println("n====迷宫路径=====n");
m1.printTwoDemensionArray(arr);
}
汉诺塔问题
public class HanoiTower {
public static void main(String[] args) {
T t = new T();
// System.out.println("=========一个盘子============");
// t.move(1, 'A', 'B', 'C');
// System.out.println("=========两个盘子============");
// t.move(2, 'A', 'B', 'C');
// System.out.println("=========三个盘子============");
// t.move(3, 'A', 'B', 'C');
System.out.println("=========5个盘子============");
t.move(5, 'A', 'B', 'C');
}
}
class T {
int count = 1; //记录移动次数
//汉诺塔移动 num ==> 盘子数 source ==> 从哪里移动 via ==> 经过 distinct ==> 到哪里
public void move(int num, char source, char via, char distinct) {
//如果塔上只有一个盘子,直接把盘子 source -> distinct
if(num == 1) {
System.out.println("从"+source+"->"+distinct+"t"+(count++));
}else{ //塔上有多个盘子 看出两个盘子
move(num - 1 , source, distinct, via); //把source上面的 num - 1 个盘子先通过 distinct 移动到 via
System.out.println("从"+source+"->"+distinct+"t"+(count++)); // 最后一个盘子直接 source -> distinct
move(num - 1, via, source, distinct); //把via上面的 num - 1 个盘子先通过 source 移动到 distinct
}
}
}
方法重载 细节和注意事项
- 方法名必须相同
- 形参列表必须不同,可以区分。参数顺序,类型,个数等等。(参数名无要求)
- 返回类型无要求。(两个**仅是返回类型不同**的方法会造成重复定义,并不会构成重载)
public int sum(int n1, int n2) {
return n1 + n2;
}
public void sum(int n1, int n2) {
int res = n1 + n2;
}
//上面两个方法重复定义,并不没有构成方法重载
可变参数
- Java中允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。
- 语法:访问修饰符 返回类型 方法名(数据类型**…** 形参名){
}
class HspMethod {
//形参列表: 数据类型 + 三个点 + 参数名 nums可以当作数组来使用
public int sum(int... nums) {
int sum = 0;
System.out.println("接收了"+nums.length+"个参数");
for( int i = 0; i < nums.length; i++) {
return sum += nums[i];
}
}
}
可变参数细节
- 可变参数的实参可以为0个或任意多个
- 可变参数的实参可以为数组
- 可变参数的本质就是数组
- 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
- —个形参列表中只能出现一个可变参数
- 全局变量(属性)可以不赋值,直接使用,因为有默认值,
- 局部变量必须赋值后,才能使用,因为没有默认值
- 全局变量(属性)和局部变量可以重名,访问时遵循就近原则。
- 在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名
- 全局变量(属性)生命周期较长,伴随着对象的创建而创建,伴随着对象的死亡而死亡
- 局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而死亡,即在一次方法调用过程中。
- 作用域范围不同
- 全局变量/属性:可以被本类使用,或其他类使用(通过对象调用)
- 局部变量:只能在本类中对应的方法中使用
- 修饰符不同
- 全局变量/属性可以加修饰符
- 局部变量不可以加修饰符
用来创建对象时,就对对象进行初始化。
语法注意事项
- 构造器的修饰符可以默认,也可以是public protected private
- 构造器没有返回值
- 方法名 和类名字必须一样,严格相同,首字母都是大写
- 参数列表 和 成员方法一样的规则
- 构造器的调用,由系统完成
public class Constructor01 {
public static void main(String[] args) {
Person p = new Person(18, "hyk");
System.out.println("名字:"+p.name+"t"+"年龄:"+p.age);
}
}
class Person {
int age;
String name;
public Person(int pAge, String pName){
age = pAge;
name = pName;
}
}
构造器使用细节
- 一个类可以定义多个不同的构造器,即构造器重载
比如:我们可以再给Person类定义一个构造器,用来创建对象的时候,只指定人名,不需要指定年龄 - 构造器名和类名要相同
- 构造器没有返回值
- 构造器是完成对象的初始化,井不是创建对象
- 在创建对象时,系统自动的调用该类的构造方法
- 如果没有定义构造器,系统会自动给类生成一个默认无参构造器(也
叫默认构造器),比如 Dog(){}。 使用javap指令反编译看看 - 一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无
参构造器,除非_显式的定义一下_,即:Person(){}
//看一个案例
class Person{//类Person
int age=90;
String name;
Person(String n,int a){//构造器
name=n;//给属性赋值
age=a;
}
}
public class Constructor {
Person p=new Person("小倩",20);
}
this 关键字:代表当前对象
public Dog(String name, int age){//构造器
//this.name 就是当前对象的属性name
this.name = name:
//this.age 就是当前对象的属性age
this.age = age;
}
//哪个对象调用这个构造器,这个this就指的是哪个对象
使用细节
- this关键字可以用来访问本类的属性、方法、构造器
- this用于区分当前类的属性和局部变量
- 访向成员方法的语法:this.方法名(参数列表)
- 访问构造器语法:this(参数列表);注意只能在构造器中使用 (即只能在构造器中访问另外一个构造器)
public T() { // 无参构造
this("yk", 18); //在构造器中访问另外一个构造器 且此语句需放置构造器的第一条
}
public T(String name, int age) { // 无参构造
System.out.println("有参构造");
}
- this不能在类定义的外部使用,只能在类定义的方法中使用
public class ThisExercise {
public static void main(String[] args) {
Person p1 = new Person(12,"张三");
Person p2 = new Person(12,"张三");
boolean res = p1.compareTo(p2);
System.out.println(res); //true
}
}
class Person {
//属性
int age;
String name;
//有参构造
public Person(int age, String name) {
this.name = name;
this.age = age;
}
//比较方法
public boolean compareTo(Person p) {
return this.name.equals(p.name) && this.age == p.age;
}
}
包
作用
- 区分相同名字的类
- 当类很多时,可以很好的管理类「看Java API 文档]
- 控制访问范围
**包基本语法** package com.hspedu; 需放在每个.java文件第一行 说明: 1. package 关键字,表示打包。 2. com.hspedu:表示包名
- java.lang.* //lang包是基本包,默认引入,不需要再引入
- java.util.* //util 包,系统提供的工具包,工具类,使用 Scanner
- java.net. * //网络包,网络开发
- java.awt.* //是做java的界面开发,GUI
java提供四种访问控制修饰符号,用于**控制方法和属性(成员变量)**的访问权限(范围):
- 公开级别:用public 修饰,对外公开
- 受保护级别:用protected修饰,对子类和同一个包中的类公开
- 默认级别:没有修饰符号,向同一个包的类公开
- 私有级别:用private修饰,只有类本身可以访问,不对外公开
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zp5fmpp9-1652873405298)(https://raw.githubusercontent.com/MCXXHYK/PicGo/main/image-20220505091456922.png#crop=0&crop=0&crop=1&crop=1&id=Ddnae&originHeight=270&originWidth=762&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)]
- 修饰符可以用来修饰类中的属性,成员方法以及类
- 只有默认的和public才能修饰类,并且遵循上述访问权限的特点。
- 因为没有学习继承,因此关于在子类中的访问权限,我们讲完子类后,再回头讲解
- 成员方法的访问规则和属性完全一样
- 将属性进行私有化private 【不能直接修改属性】
- 提供一个公共的(public)set方法,用于对属性判断并赋值
public void setXxx(类型 参数名){ //Xxx 表示某个属性
//加入数据验证的业务逻辑
属性=参数名;
}
- 提供一个公共的(public)get方法,用于获取属性的值
public 数据类型 getXxx(){ //权限判断,Xxx 某个属性
return xx;
}
PS:有参构造器可以和set方法一起使用,用来控制权限
继承 基本介绍继承可以解决代码复用,让我们的编程更加靠近人类思维,当多个类存在相同的属性(变量)和方法时,可以以这些类中抽象出父类,在父类中定义这些相同的属佳和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来声明继承父类即可。
语法class 子类 extends 父类{
}
//1)子类就会自动拥有父类定义的属性和方法
//2)父类又叫超类,基类.
//3)子类又叫派生类.
细节
- 子类继承了所有的属性和方法,非私有属性可以直接访问但是私有属性和方法不能在子类直接访向,要通过父类所提供的公共的方法间接去访问
- 子类必须调用父类的构造器,完成父类的初始化(子类构造器中隐藏了super() )
- 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器
- 如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过.
#【解读】: #1.父类中有无参构造器时,子类中不管是有参构造器还是无参构造器都会隐藏一个super() ,默认调用父类的无参构造器。 #2.父类中没有无参构造器时, # 即父类中有参构造器将默认无参构造器覆盖掉时, # 且没有在父类中另外重新声明无参构造器, # 则在子类的构造器中必须用super去指定使用父类的哪个构造器 # 完成对父类的初始化,否则编译无法通过。 #【另注】:初始化子类时,父类也会被初始化,且父类先被初始化
- 如果希望指定去调用父类的某个构造器,则显式的调用一下:super(参数列表),默认调用的是无参构造器
- super在使用时,需要放在构造器第一行
- super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
- java所有类都是Object类的子类,Object 是所有类的基类。
- 父类构造器的调用不限于直接父类!将一直往上追湖直到Object类(顶级父类),从上往下调用构造器
- 子类最多只能继承一个父类(指直接继承),即java中是单继承机制
- 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
- 子类对象访问属性或调用方法时注意事项:
- 首先看子类是否有该属性
- 如果子类中有该属性,并且可以直接访问(没有private),则返回信息
- 如果子类中没有该属性,就看其直接父类(上一层父类)有没有该属性,如果有,就停止查找,并且该属性可以直接访问(没有private),则返回信息(如果这里属性如果被私有化了,别的类的不能访问,则子类可以通过父类提供的公共get/set方法访问该属性)
- 如果直接父类中还没有该属性,则再向上找,直到有该属性并且可以访问的父类(就近原则,此"近"是指距离子类的距离)
package com.test.extend_;
public class extend_test {
public static void main(String[] args) {
Son son = new Son();
System.out.println(son.name); //小头儿子
System.out.println(son.age); //30
System.out.println(son.hobby); //旅游
}
}
class GrandPa {
String name = "超大头爷爷";
String hobby = "旅游";
}
class Father extends GrandPa {
String name = "大头爸爸";
int age = 30;
}
class Son extends Father {
String name = "小头儿子";
}
//如果这里属性如果被私有化了,别的类的不能访问,则子类可以通过父类提供的公共get/set方法访问该属性。
System.out.println(a.sum()); //40 -> 30
System.out.println(a.sum1()); //30 -> 20
}
}
class A { //父类
public int i = 10;
public int sum() {
return getI() + 10;
}
public int sum1() {
return i + 10;
}
public int getI() {
return i;
}
}
class B extends A {//子类
public int i = 20;
// public int sum() {
// return i + 20;
// }
public int getI() {
return i;
}
// public int sum1() {
// return i + 10;
// }
}
多态的应用
- 多态数组:数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
package com.poly.polyArray;
public class Poly_Array {
public static void main(String[] args) {
Person[] person = new Person[5];
person[0] = new Person("yk", 18);
person[1] = new Student("小明", 8, 59.5);
person[2] = new Student("小刚", 9, 99.5);
person[3] = new Teacher("smith", 30, 10000);
person[4] = new Teacher("武忠祥",60,200000);
for (int i = 0; i < person.length ; i++) {
//如果数组中元素不是Student类或者其子类
if( person[i] instanceof Student) {
Student stu = (Student) person[i];
stu.study();
//等价于
//((Student) person[i]).study();
}else if (person[i] instanceof Teacher) {
((Teacher) person[i]).teach();
}else if (person[i] instanceof Person) {
}else{
System.out.println("你的类型有误,请检查!");
}
// System.out.println(person[i].say());
}
}
}
- 多态参数:方法定义的形参类型为父类类型,实参允许为子类类型。
- equals()方法
- equals() 和 == 的对比:
- ==:既可以判断基本类型,又可以判断引用类型
- ==:如果判断基本类型,判断的是值是否相等。示例:int i=10;double d=10.0;
- ==:如果判断引用类型,判断的是地址是否相等,即判定是不是同一个对象
- equals():是Object类中的方法,只能判断引用类型
- equals():默认判断的是地址是否相等, 子类中往往重写该方法,用于判断内容是否相等。
- equals() 和 == 的对比:
package com.poly.equals;
public class Equals_Detail {
public static void main(String[] args) {
Equals_Test t1= new Equals_Test(18,"jack",20000);
Equals_Test t2= new Equals_Test(18,"jack",20000);
System.out.println(t1.equals(t2));;
}
}
package com.poly.equals;
public class Equals_Test {
private int age;
private String name;
private int id;
public Equals_Test(int age, String name, int id) {
this.age = age;
this.name = name;
this.id = id;
}
public boolean equals(Object o) {
if( o != null && o == this ) {
return true;
}else if(o instanceof Equals_Test) {
Equals_Test e = (Equals_Test) o;
return e.age == age && e.name.equals(name) && e.id == id;
}
return false;
}
}
- hashCode()方法:返回该对象的哈希码值
- 提高具有哈希结构的容器的效率!
- 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
- 两个引用,如果指向的是不同对象,则哈希值是不一样的
- 哈希值主要根据地址来的,但是不能完全将哈希值等价于地址。
- 案例演示IHashCode_javal: obj。hashCode0[测试:A obj1= new A0; A obj2 = new A0: A
obj3 = obj1]
- 案例演示IHashCode_javal: obj。hashCode0[测试:A obj1= new A0; A obj2 = new A0: A
- 后面在集合,中hashCode 如果需要的话,也会重写
- toString()方法
- 基本介绍
- 默认返回:全类名+@+哈希值的十六进制, 子类往往重写toString方法,用于返回对象的属性信息
重写toString方法**,打印对象或拼接对象时,都会自动调用该对象的toString形式**. - 当直接输出一个对象时,toString 方法会被默认的调用,比如
System.out.printin(monster);
就会默认调用 monster.toString()
- 默认返回:全类名+@+哈希值的十六进制, 子类往往重写toString方法,用于返回对象的属性信息
- 基本介绍
@Override
public String toString() {
return "Monster{" +
"name='" + name + ''' +
", job='" + job + ''' +
", sal=" + sal +
'}';
}
- finalize()方法
- 当对象被回收时,系统自动调用该对象的finalize方法。子类可以重写该方法,
做一些释放资源的操作 - 什么时候被回收:当某个对象没有任何引用时,则 jvm就认为这个对象是一个垃
圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前,会先调用finalize方法。 - 垃圾回收机制的调用,是由系统来决定,也可以通过System.gc() 主动触发垃圾
回收机制
- 当对象被回收时,系统自动调用该对象的finalize方法。子类可以重写该方法,
- 断点调试
- F7(跳入)F8(跳过) shift+ F8(跳出) F9( resume,执行到下一个断点 )
- F7**:跳入方法内**
- F8:逐行执行代码.
- shift+F8:跳出方法
实现基于文本界面的《房屋出租软件》。能够实现对房屋信息的添加、修改和删除(用数组实现),并能够打印房屋明细表。
类变量(静态变量) 定义:类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的
对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改
的也是同一个变量。
- 类变量由关键字static修饰,其最大的特点就是会被类中所有对象共享
- 类变量可以通过类名直接访问
- 类变量在类加载时就生成了
【访问修饰符】 static 【数据类型】 变量名(推荐) 或者 static 【访问修饰符】【数据类型】 变量名访问类变量
类名.变量名 (推荐) 或者 对象名.变量名使用细节
- 什么时候需要用类变量
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态
变量) - 类变量与实例变量(普通属性)区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的。 - 加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
类变量可以通过 类名.类变量名 或者 对象名.类变量名 来访问,但java设计者推荐
我们使用 类名.类变量名方式访问。【前提是满足访问修饰符的访问权限和范围】 - 实例变量不能通过 类名.类变量名 方式访问。
- 类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,
就可以使用类变量了。 - 类变量的生命周期是随类的加载开始,随着类消亡而销毁
【访问修饰符】 static 【数据类型】 方法名 {}(推荐)
或者
static 【访问修饰符】【数据类型】 方法名 {}
类方法调用
类名.类方法名 (推荐) 或者 对象名.类方法名 【前提】:满足访问修饰符的访问权限细节
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:类方法中无this的参数
- 普通方法中隐含着this的参数
- 类方法可以通过类名调用,也可以通过对象名调用。
- 普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调
用 - 类方法中**不允许使用和对象有关的关键字,比如this和super,**普通方法(成员方法)可以。
- 类方法中只能访问静态变量或静态方法
- **普通成员方法,既可以访问 非静态成员,也可以访问静态成员 **。
public static void main(String[] args) {
}
- main方法时虛拟机调用
- java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
- java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
- 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所
运行的类的参数 - java 执行的程序 参数1参数2 参数3
javac Hello.java java Hello tom jack smith #这里tom jack smith 就是传入String数组的参数,执行命令时传入特别提示
- 在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性。
- 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能
通过这个对象去访问类中的非静态成员
- 代码化块又称为初始化块,属于类中的成员[即是类的一部分],类似于方法,将逻辑语句
封装在方法体中,通过{ }包围起来。 - 但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或
类显式调用,而是加载类时,或创建对象时隐式调用
[修饰符]{
代码
}
说明注意:
- 修饰符要么不写,要么用static
- 代码块分为两类,使用static 修饰的叫静态代码块,没有static修饰的,叫普通代码块/非静态代码块。
- 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
- 结尾括号的分号可以写上,也可以省略。
- 调用构造器时会先调用代码块中的内容
- 使用场景:多个构造器中有相同的重复语句,可以将其抽取到代码块中。
- 普通代码块可以理解为是对构造器的补充机制,可以做初始化操作
- static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且
只会执行一次。如果是普通代码块,每创建一个对象,就执行。 - 类什么时候被加载?
- 创建对象实例时(new)
- 创建子类对象实例,父类也会被加载;而且父类先被加载,子类后被加载
- 使用类的静态成员时(静态属性,静态方法)
- 普通的代码块,在创建对象实例时,才会被隐式的调用。被创建一次,就会调用一次。
- 如果只是使用类的静态成员时,普通代码块并不会执行。
- 创建一个对象时,在一个类 调用顺序是:(重点,难点)
- 调用静态代码块和静态属性初始化(注意:静态代码块和静态
属性初始化调用的优先级一样,如果有多个静态代码块和多个静
态变量初始化,则按他们定义的顺序调用)[举例说明] - 调用普通代码块和普通属性的初始化(注意:普通代码块和普
通属性初始化调用的优先级一样,如果有多个普通代码块和多
个普通属性初始化,则按定义顺序调用) - 调用构造方法。
- 调用静态代码块和静态属性初始化(注意:静态代码块和静态
- 构造器的最前面其实隐含了 supe()和 调用普通代码块;
- 静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行的
package com.ykstudy.code_block;
public class CodeBlock {
public static void main(String[] args) {
new BB();
}
}
class AA {
{
System.out.println("AA的代码块");
}
public AA() {
//(1)super()
//(2)本类的普通代码块
System.out.println("AA的构造器被调用");
}
}
class BB extends AA {
{
System.out.println("BB的代码块");
}
public BB() {
//(1)super()
//(2)本类的普通代码块
System.out.println("BB的构造器被调用");
}
}
- 我们看一下创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
- 父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
- 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 父类的构造方法
- 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
- 子类的构造方法
- 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。
class A02 { //父类
private static int n1 = getVal01();
static {
System.out.println("A02的一个静态代码块..");//(2)
}
{
System.out.println("A02的第一个普通代码块..");//(5)
}
public int n3 = getVal02();//普通属性的初始化
public static int getVal01() {
System.out.println("getVal01");//(1)
return 10;
}
public int getVal02() {
System.out.println("getVal02");//(6)
return 10;
}
public A02() {//构造器
//隐藏
//super()
//普通代码和普通属性的初始化......
System.out.println("A02的构造器");//(7)
}
}
class B02 extends A02 { //
private static int n3 = getVal03();
static {
System.out.println("B02的一个静态代码块..");//(4)
}
public int n5 = getVal04();
{
System.out.println("B02的第一个普通代码块..");//(9)
}
public static int getVal03() {
System.out.println("getVal03");//(3)
return 10;
}
public int getVal04() {
System.out.println("getVal04");//(8)
return 10;
}
//一定要慢慢的去品..
public B02() {//构造器
//隐藏了
//super()
//普通代码块和普通属性的初始化...
System.out.println("B02的构造器");//(10)
}
}
public Class Code_Block_Detail {
public static void main(String[] args) {
new B02();//分析代码执行顺序
}
}
设计模式
单例设计模式
- 说明:从运行到结束的过程中,某个类只有一个实例存在
- 步骤
- 构造器私有化 => 防止直接 new
- 类的内部创建对象
- **向外暴露一个静态的公共方法。**例如:public void getInstance(){ }
- 步骤
- 构造器私有化 ==> 防止直接new
- 定义一个私有静态变量(不初始化)保存类的对象
- 提供一个公共静态方法返回类的对象
- 判断类中保存对象的静态变量有没有被初始化
package com.ykstudy.single_;
public class Single_ton01 {
public static void main(String[] args) {
//Girlfriend{name='小红'} 饿汉式
System.out.println(Girlfriend01.getInstance());
//Girlfriend02{name='美羊羊'} 懒汉式
System.out.println(Girlfriend02.getInstance());
}
}
//饿汉式
class Girlfriend01 {
private String name;
//2.在类的内部定义私有的静态变量保存新创建的对象
private static Girlfriend01 gf = new Girlfriend01("小红");
//1.构造器私有化
private Girlfriend01(String name) {
this.name = name;
}
//3.对外部提供一个公有的静态方法
public static Girlfriend01 getInstance() {
return gf;
}
@Override
public String toString() {
return "Girlfriend{" +
"name='" + name + ''' +
'}';
}
}
//懒汉式
class Girlfriend02 {
private String name;
//2.定义一个私有静态变量(不初始化)保存类的对象
private static Girlfriend02 gf;
//1.构造器私有化
public Girlfriend02(String name) {
this.name = name;
}
//3.提供一个公共静态方法返回类的对象
public static Girlfriend02 getInstance() {
//4.判断类中保存对象的静态变量有没有被初始化
if(gf == null) {
gf = new Girlfriend02("美羊羊");
}
return gf;
}
@Override
public String toString() {
return "Girlfriend02{" +
"name='" + name + ''' +
'}';
}
}
两种方式存在的问题(饿汉式 || 懒汉式)
- 饿汉式的问题:在类加载时候就创建,可能存在资源浪费问题
- 懒汉式的问题:线程安全问题,后面我们学了线程后,在进行完善。
final 中文意思:最后的,最终的.
final 可以修饰类、属性、方法和局部变量.
在某些情况下,程序员可能有以下需求,就会使用到final:
- **当不希望类被继承时,**可以用final修饰.
- 当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰。
- 当不希望类的的某个属性的值被修改,可以用final修饰.
- 当不希望某个局部变量被修改,可以使用final修饰
- final修饰的属性又叫常量,一般用 XX_XX_XX来命名
- final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一【选择一个位置赋初值即可】:
- 定义时:如 public final double TAX RATE=0.08;
- 在构造器中
- 在代码块中
- 如果final修饰的属性是静态的,则初始化的位置只能是
- 定义时
- 在静态代码块
- 但是不能在构造器中赋值。
- final类不能继承,但是可以实例化对象。
- 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承。
- 一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法。
- final不能修饰构造方法(即构造器)
- final 和 static 往往搭配使用,效率更高,修饰基本数据类型时不会导致类加载.底层编译器做了优化处理。
class Demo{
public static final int i=16; //
static{
System.out.printiln(”韩顺平教育~");
}
- 包装类(Integer,Double, Float,Boolean等都是final类),String也是final类。
当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法,这个方法就是抽象方法,然后用abstract 来修饰该类就是抽象类。
//我们看看如何把Animal做成抽象类,并让子类Cat类实现。
abstract class Animal{
String name;
int age;
abstract public void cry(); //抽象方法没有方法体,写成抽象方法后,类也要加abstract修饰
}
细节和注意事项
- 抽象类不能被实例化
- 抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法
- 一旦类包含了abstract方法,则这个类必须声明为abstract
- abstract 只能修饰类和方法,不能修饰属性和其它的。
- 抽象类可以拥有任意成员【抽象类的本质还是类】
- 抽象方法没有方法体,即不能在本类实现。
- 抽象类一旦被继承,其子类必须实现其所有抽象方法。除非其子类也被abstract修饰。
- 抽象方法不能使用private、 final 和 static来修饰,因为这些关键字都是和重写相违背的
public class Template_Mode {
public static void main(String[] args) {
B b = new B();
b.calculateTime();
}
}
abstract class A {
//定义一个抽象方法,子类重写
public abstract void job();
//把抽象方法塞到这个普通方法里面形成模板
public void calculateTime() {
//开始时间
long start = System.currentTimeMillis();
//实现任务
job(); //动态绑定机制
//结束时间
long end = System.currentTimeMillis();
System.out.println("任务时间==" + (end - start)); //137
}
}
class B extends A {
@Override
public void job() {
long num = 0;
for (long i = 0; i < 100000000; i++) {
num *= i;
}
}
}
接口
- 接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,在根据具体情况把这些方法写出来。
语法:
interface 接口名{
//属性
//方法(默认抽象方法;default修饰的方法;static修饰的方法)
int num;
//接口中方法默认为抽象方法
public void cry();
//jdk8以后接口中方法加关键词 static default修饰时方法可以有方法体
static void smile() {
System.out.println("haha");
}
default void play() {
System.out.println("playing now");
}
}
class 类名 implements 接口名{
}
小结:
- 在Jdk7.0前接口里的所有方法都没有方法体。
2.Jdk8.0后接口类可以有静态方法,默认方法,也就是说接口中可以有方法的具体实现
-
接口不能被实例化
-
接口中所有的方法是 public方法,接口中抽象方法,可以不用abstract 修饰
void aa() 实际上是 public abstract void aa();
-
一个普通类实现接口,就必须将该接口的所有方法都实现。
-
抽象类实现接口,可以不用实现接口的方法。
-
一个类同时可以实现多个接口
-
接口中的属性,只能是final的,而且是 public static final 修饰符。比如:
int a=1;实际上是 public static final int a=1;(必须初始化)
接口中属性的访问形式:接口名.属性名 -
一个接口不能继承其它的类,但是可以继承多个别的接口
interface A extends B, C {
}
interface B {
}
interface C {
}
- 接口的修饰符只能是 public 和默认,这点和类的修饰符是一样的。
- 实现接口是对Java单继承的一种补充
- 当子类继承了父类,就自动的拥有了父类的功能
- 如果子类需要扩展功能,可以通过实现接口的方式扩展
- 继承的价值:解决代码的复用性和可维护性
- 接口的价值:设计,设计好各种规范(方法),让其他类去实现这些方法
- 接口比继承更加灵活
继承是is a的关系,而接口只需要满足like a的关系 - 接口在一定程度上实现代码的解耦【即:接口规范性 + 动态绑定】
比喻:猴子继承爬树,可以实现鱼一样的游泳,可以实现小鸟一样飞翔
- 接口多态参数:接口类型的变量可以指向实现了此接口的对象实例
public class A {
public static void main(String[] args) {
//接口类型的变量可以指向实现了此接口的对象实例
IF if = new B();
}
}
Interface IF {
}
class B implements IF {
}
- 接口多态数组
- 接口多态传递
public class InterfacePolyPass {
public static void main(String[] args) {
//接口类型的变量可以指向,实现了该接口的类的对象实例
IG ig = new Teacher();
//如果IG 继承了 IH 接口,而Teacher 类实现了IG接口
//那么,实际上就相当于 Teacher 类也实现了IH接口
IH ih = new Teacher();
}
}
interface IH { }
interface IG extends IH{ }
class Teacher implements IG {
}
内部类
基本介绍
一个类的内部又完整的嵌套了另一个类结构,被嵌套的类称为内部类(inner class), 嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员.
- 【思考】:类的五大成员是哪些?[属性,方法、构造器、代码块、内部类],
- 内部类最大的特点就是可以直接访问私有属性,井且可以体现类与类之间的包含关系,
- 注意:内部类是学习的难点,同时也是重点,后面看底层源码时,有大量的内部类.
class Outer{ //外部类
class Inner{ //内部类
}
class Other{ //外部其他类
}
内部类的分类
1. 局部内部类
【说明】:局部内部类是定义在外部类的局部位置,比如方法中,代码块中,并且有类名。
- 可以直接访问外部类的所有成员,包含私有的
- 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final 修饰,因为局部变量也可以使用final
- 作用域:仅仅在定义它的方法或代码块中。
- 局部内部类—访问 ==> 外部类的成员 [访问方式:直接访问]
- 外部类—访问===>局部内部类的成员
访问方式:创建对象,再访问(注意:必须在作用域内) - 外部其他类—不能访问====>局部内部类(因为局部内部类地位是一个局部变量)
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
【演示】
//Outero2.this 本质就是外部类的对象,即哪个对象调用了m1, Outero2.this就是哪个对象
System.out printin("外部类的n2-”+外部类名.this.n2);
2. 匿名内部类(重要‼️)
【说明】:匿名内部类是定义在外部类的局部位置,比如方法中,井且没有类名
(1) 本质是类;
** (2)内部类**
** (3)没有名字(系统底层分配名字)**
** (4)同时还是一个对象!!!**
- 匿名内部类的基本语法
new 类或接口(参数列表){
类体
};
【细节】:
- 可以直接访问外部类的所有成员,包含私有的
- 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final 修饰,因为局部变量也可以使用final
- 作用域:仅仅在定义它的方法或代码块中。
- 局部内部类—访问 ==> 外部类的成员 [访问方式:直接访问]
- 外部类—访问===>局部内部类的成员
访问方式:创建对象,再访问(注意:必须在作用域内) - 外部其他类—不能访问====>局部内部类(因为局部内部类地位是一个局部变量)
- 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
【特点】:
- 没有类名的局部内部类(一切特征都与局部内部类相同)
- 必须继承一个父类或者实现一个接口
- 定义类、实现类、创建对象的语法合并,只能创建一个该类的对象
- 优点:减少代码量
- 缺点可读性较差
// 使用匿名内部类优化(相当于创建了一个局部内部类)
Usb usb = new Usb(){ // Usb为一个接口
@Override
public void service(){
sout("连接电脑成功,fan开始工作")
}
};
usb.service();
匿名内部类最佳实践
- 匿名内部类用作实参直接传递
public class AnoymousInnerClass {
public static void main(String[] args) {
Cellphone cellphone = new Cellphone();
cellphone.alarmClock(new Bell() {
@Override
public void ring() {
System.out.println("懒猪起床了!");
System.out.println(this.getClass()); //class AnoymousInnerClass$1
}
});
cellphone.alarmClock(new Bell() {
@Override
public void ring() {
System.out.println("小伙伴下课了!");
}
});
}
}
interface Bell {
void ring();
}
class Cellphone {
public void alarmClock(Bell bell) {
bell.ring();
}
}
3. 成员内部类
说明:成员内部类是定义在外部类的成员位置,并且没有static修饰。
- 可以直接访问外部类的所有成员,包含私有的
class Outer01{ //外部类 private int n1 = 10;
public String name = "张三”;
class Innter01{
public void say(){
System.out.println("Outer01 的n1 =" + n1
+ "outer01 的 name "+ name );
}
}
}
- 可以添加任意访问修饰符(public. protected、默认、Private),因为它的地位就是一个成员。
- 作用域:和外部类的其他成员一样,为整个类体
比如前面案例,**在外部类的成员方法中创建成员内部类对象,再调用方法. **
- 成**员内部类—访问---->外部类(比如:属性)[访问方式:直接访问] **
- 外部类—访问------>内部类(说明) 访问方式:创建对象,再访问
- 外部其他类—访问---->成员内部类
//外部其他类访问成员内部类两种方式
//1.第一种方法 outter.new Inner() 相当于把new Inner() 当作outter的成员
Outter.Inner inner = outter.new Inner();
//2.在外部类中编写一个方法可以返回 Inner 对象
class Outer{ //外部类 private int n1 = 10;
public String name = "张三”;
class Innter{
public void say(){
System.out.println("Outer01 的n1 =" + n1
+ "outer01 的 name "+ name );
}
}
//第二种方式:在外部类中编写一个方法可以返回 Inner 对象
public Inner getInner() {
return new Inner();
}
}
public class Test {
public static void main(String[] args) {
Outter outter = new Outter();
//第一种方式
Outter.Inner inner = outter.new Inner();
}
}
- 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
【说明】:静态内部类是定义在外部类的成员位置,井且有static修饰
- 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
- 可以添加任意访问修饰符(public. protected、默认、private),因为它的地位就是一个成员。
- 作用域**:同其他的成员,为整个类体**
class Outer02{//外部类 private int n1 = 10;
private static String name = “张三”;
static class Inner02{
public void say(){
System.out.println(name);
//不能直接访问外部类的非静态成员
//System.out.println(n1);
}
}
public void show(){
//外部类使用内部类 new Inner020.say();
}
}
- 静态内部类—访问---->外部类(比如:静态属性)[访问方式:直接访问所有静态成员]
- 外部类—访问----->静态内部类访问方式:创建对象,再访问
- 如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问
【说明】:
- 枚举对应英文(enumeration, 简写 enum)
- 枚举是一组常量的集合。
- 可以这里理解:枚举属于一种特殊的类,里面只包含一组有限的特定的对象。
- 构造器私有化
- 本类内部创建一组对象
- 对外暴露对象(通过为对象添加public final static修饰符)
- 可以提供 get()方法,但是不要提供 set()方法
package com.yk.enum_;
public class Enumeration01 {
public static void main(String[] args) {
System.out.println(Season.SPRING);
}
}
class Season {
private String name;
private String weather;
//2.本类创建一组公开常量对象 3.对外暴露对象(通过对象添加 public static final 修饰符)
public static final Season SPRING = new Season("春天","温暖");
public static final Season SUMMER = new Season("夏天","炎热");
public static final Season AUTUMN = new Season("秋天","凉爽");
public static final Season WINTER = new Season("冬天","寒冷");
//1.构造器私有化
private Season(String name, String weather) {
this.weather = weather;
this.name = name;
}
//4.可以提供get()方法 但是不要提高set()方法
public String getName() {
return name;
}
public String getWeather() {
return weather;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + ''' +
", weather='" + weather + ''' +
'}';
}
}
关键字 enum 实现枚举
- 使用关键字 enum 替代 class
- public static final Season SPRING = new Season(“春天”,“温暖”)直接使用 SPRING(“春天”,“温暖”)
解读:常量名(实参列表)
- 如果有多个常量(对象),使用** , **号间隔即可
- 使用enum实现枚举, 要求将定义常量对象写在最前面
package com.yk.enum_;
public class Enumeration01 {
public static void main(String[] args) {
System.out.println(Season.SPRING);
}
}
//1. class ==> enum
enum Season {
//2.public static final Season SPRING = new Season("春天","温暖")
//直接使用 SPRING("春天","温暖") 枚举常量对象写在最前面
SPRING("春天" ,"温暖") ,
SUMMER("夏天","炎热"),
AUTUMN("秋天","凉爽"),
WINTER("冬天","寒冷");
private String name;
private String weather;
//构造器私有化
private Season(String name, String weather) {
this.weather = weather;
this.name = name;
}
//可以提供get()方法 但是不要提高set()方法
public String getName() {
return name;
}
public String getWeather() {
return weather;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + ''' +
", weather='" + weather + ''' +
'}';
}
}
enum 实现枚举类注意事项
- 当我们使用enum 关键字实现一个枚举类时,默认会继承Enum类,并且此时该枚举类是一个final修饰的类[如何证明:使用javap 工具反编译]
- 传统的 public static final Season SPRING = new Season(“春天”,"温暖 ");简化成 SPRING("春天””温暖”),这里必须知道,它调用的是哪个构造器,通过实参可以看出来.
- 如果使用无参构造器创建枚举对象,则实参列表和 () 都可以省略
- 当有多个枚举对象时,使用,间隔,最后有一个分号结尾
- 枚举对象必须放在枚举类的行首
- 枚举类中构造器是私有的
- enum 实现的枚举类虽然不能继承其他类了(因为存在隐式继承,继承Enum类),但是可以实现接口
package com.yk.enum_;
public class Enumeration01 {
public static void main(String[] args) {
//toString: Season{name='春天', weather='温暖'}
System.out.println(Season.SPRING);
//values: 返回当前枚举类中所有的常量,返回的是 enum[] 类型
Season[] seasons = Season.values();
//增强for循环 从数组seasons中依次取出元素
for (Season season : seasons) {
System.out.println(season);
}
//name: 返回当前对象名(常量名),子类中不能重写
System.out.println(Season.SPRING.name()); //SPRING
//ordinal:返回当前对象的位置号,默认从0开始
System.out.println(Season.SPRING.ordinal()); // 0
//valueOf:将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常!
Season season1 = Season.valueOf("AUTUMN");
System.out.println(season1);
//compareTo:比较两个枚举常量,比较的就是编号!
System.out.println(Season.SPRING.compareTo(Season.SUMMER)); //-1
}
}
enum Season {
SPRING("春天" ,"温暖") ,
SUMMER("夏天","炎热"),
AUTUMN("秋天","凉爽"),
WINTER("冬天","寒冷");
private String name;
private String weather;
//构造器私有化
private Season(String name, String weather) {
this.weather = weather;
this.name = name;
}
//可以提供get()方法 但是不要提高set()方法
public String getName() {
return name;
}
public String getWeather() {
return weather;
}
@Override
public String toString() {
return "Season{" +
"name='" + name + ''' +
", weather='" + weather + ''' +
'}';
}
}
注解(Annotation)
【说明】:
- 注解(Annotation)也被称为元数据(Metadata),用于修饰解释包、 类、方法、属性、构造器、局部变量等数据信息。
- 和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。
- 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替java EE旧版中所遗留的繁冗代码和XML配置等。
- @Override 表示指定重写父类的方法(从编译层面验证),如果父类没有fly方法,则会报错
- 如果不写@Override 注解,而父类仍有 public void fly(){},仍然构成重写
- @Override 只能修饰方法,不能修饰其它类,包,属性等等
- 查看@Override注解源码为 @Target(ElementType.METHOD),说明只能修饰方法
- @Target 是修饰注解的注解,称为元注解
- 表示某个元素(类或者方法)已过时
- 过时并不代表不能用,只是不建议用
【说明】:
- 用于表示某个程序元素(类,方法等)己过时
- 可以修饰方法,类,字段,包,参数等等
- @Target(value={CONSTRUCTOR, FIELD, LOCAL VARIABLE, METHOD. PACKAGE, PARAMETER, TYPE})
- @Deprecated 的作用可以做到新旧版本的兼容和过渡
- **@Retention ** //指定注解的作用范围,三种 SOURCE,CLASS,RUNTIME
- @Target // 指定注解可以在哪些地方使用
- @Documented //指定该注解是否会在javadoc体现
- **@Inherited **//子类会继承父类注解



