前言:
本人在CSDN当中看过不少Java的知识树、知识点总结、学习路线。
但个人认为太多跳转页复习起来效率不高,因此决定自制一份学习路线和知识点总结为一体的Java知识点全书。总结了自己Java学习过程中项目和知识点遇到的难题。
足够的通俗易懂、语言表述谨慎、算得是很全面!欢迎指错。
细心看完一定能够巩固好Java基础、共勉!
带有标记知识点皆为难点与重点!
更新了四万字,Markdown打字延迟严重、因此只能另开一篇文章
JavaSE(下)
空间大小(字节):byte[1] short[2] int[4] long[8] float[4] double[8]
长数值: 导入 java.util.math.* (BigInteger/BigDecimal)
在java中有两个类BigInteger
和BigDecimal
分别表示大整数类、大浮点数类
BigInteger的计算:https://blog.csdn.net/PGZXB/article/details/118765888
浮点数在机器中存放形式: 浮点数 = 符号位 + 指数位 + 尾数位
尾数部分可能丢失,造成精度损失(小数都是近似值)
科学计数法输出: 5.12e2 [5.12 * 10²]
char字符的本质是整数
Java使用float/double时在数据值后添加 f/lf ; Long 在数据值后添加 L ;
boolean
无法将其他数据类型强制转换成boolean类型
Java中不能用 0 和 非0 整数来代替false和true (与C语言不同)
Java在程序进行赋值或运算时,精度小的类型会自动转换为精度大的数据类型
以下是数据类型按精度(容量)大小排序规则 (自动转换路线)
char → int → long → float → double
byte → short → int → long → float → double
byte,short 和 char 任一参加运算时,都会自动转换成 int
int a = 'c'; double d = 80; //都是对的✅
int n1 = 10;
float d0 = n1 + 1.1;//错误❌ ----结果是double
double d1 = n1 + 1.1;//正确✅ ----结果是double
float d2 = n1 + 1.1;//正确✅ ----结果是float
多种类型数据混合运算时,系统会先把所有数据变成其中数据容量最大的再计算。
容量大数据类型 → 容量小数据类型 使用强制转换符()
①
强制转换符值针对最近的操作数字有效(可以利用括号提升优先级)
②从大到小强制转换会精度损失或数据溢出
③char类型可以保存 正数 但不能用int类型的变量给它赋值
int i = (int)1.9;// 1
int n1 = 2000; byte b2 = (byte)n2;// -48
//以上是精度损失和数据溢出展示
int x = (int)10*3.5+6*1.5;//会变成double数据类型
//以上是强制转换符支队最近操作数有效展示
一个 " " 双引号括起来轻松拿下 !
int n1 = 100; String s1 = n1 + "";//s1即为n1的字符串形式
//以上是基本类型转String类型
String s5 = "123";
int sb = Integer.parseInt(s5);
double sbb = Double.parseDouble(s5);
//以上是字符型转换成基本类型
System.out.println(s5.chatAt(0));//得到s5这个字符串的第一个字符
加号 + 两个字符变量会变成一个正数值 ; 加号 + 两个字符串变量会把内容连在一起输出
代码如下(示例):
相对路径: 从当前目录开始定位,形成的一个路径
绝对路径: 从顶级目录开始定位,形成的路径
1.查看目录内容:dir d:/boke/blog
2.进入XXX文件夹: cd d:/boke
3.回到上一层文件夹: cd …
4.回到根目录: cd/
提示:导包:import java.util.Scanner;
Scanner sc = new Scanner(System.in);//sc为变量名
int i = sc.nextInt();//i为变量名,sc需要和上方语句对应
int i2 = sc.nextInt();//C无法直接在运行后输入两次变量,Java可以
提示:Java使用float/double时在数据值后添加 L/F
System.out.println(10 / 4); // 2
System.out.println(10.0 / 4); // 2.5
double d = 10 / 4; // 先10 / 4 = 2, 2=>2.0;
% 模的本质是 a % b = a - a / b * b;
-10 % 3 => -10 - (-10) / 3 * 3 = 10
& :逻辑与: 第一个条件false,第二个也要判断,效率低。
&&:短路与: 第一个条件false,结果就是false,后面条件都不看。
| : 逻辑或: 不论第一个是什么,第二个都要判断。
||: 短路或: 第一个条件为true,结果为true。
! :取反: true 变 false ,false 变 true 。
^ 逻辑异或: a ^ b, ab不相同时true,相同为false。
基本赋值运算符: int a = 10;
复合赋值运算符: +=、-=、/=、%=等等
a += b; 即 a = a + b;
条件表达式 ? 表达式1:表达式2;
运算规则:
1.如果条件表达式为true,运算后的结果是表达式1
2.如果条件表达式为false,运算后的结果是表达式2
3.表达式1和表达式2要为可以赋给接收变量的类型(或可以自动转换)
三元运算符是一个整体,运算表达式数据会自动转换为表达式中最高精度然后返回
4.三元运算符本质就是if--else
语句
1.( ) , { }等等
2.单目运行 ++ –
3.算术运算符
4.位移运算符
5.比较运算符
6.逻辑运算符
7.三元运算符
8.赋值运算符
规则(必须遵守
):
1.由26个英文字母大小写,0-9,7或 $组成
2.数字不可以开头。int 3ab = 1; // 错误
3.不可以使用关键字和保留字,但能包含关键字和保留字
4.Java中严格区分大小写,长度无限制。int totalNum = 10;int n = 905;
5.标识符不能包含空格。int a b = 90;
规范:
1.包名: 多单词组成时所有字母都小写: aaa.bbb.ccc //比如 com.hsp.crm
2.类名、接口名: 多单词组成时,所有单词的首字母大写: XxxYyyZzz比如:TankShotGame
3.变量名、方法名: 多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写: xxxYyyZzz比: tankShotGame
4.常量名: 所有字母都大写。多单词时每个单词用下划线连接: XXX YYY ZZZ比如 :定义一个所得税率 TAX RATE
5.后面我们学习到 类,包,接口,等时,我们的命名规范要这样遵守,更加详细的看文档.
1.导入该类的所在包 java.util.*
2.创建该类对象(声明变量)
3.调用里面的功能
Scanner s = new Scanner(System.in);
int r = s.nextInt();
按权展开法(R进制
转10进制
):
短除法(10进制
转R进制
): 将余数倒过来输出得到二进制
2进制
转8、16进制
:
原码:第一位符号位
、其他位置普通二进制
反码:正数不变,第一位符号位
、其他位置在原码基础上 1变0、0变1
移码:用来做浮点运算当中的接码
、在补码基础上 首位1变0、0变1
科学计数法的两位数计算:对阶 → 尾数计算 → 结果格式化
N = 尾数 * 基数^指数
1.java的七个位运算(&、|、……、~、>>、<<、>>>)
分别为 按位与&、按位或|、按位异或^、按位取反~:
①:按位与&:两位全为1,结果为1,否则为0
②:按位或|:两位有一个为1,结果为1,否则为0
③:按位异或^:两位一个为0,一个为1,结果为1,否则为0
④:按位取反~:0 -> 1,1 -> 0;
比如: 2&3 计算出2和3的补码分别为: 00000010、00000011 每一位相比较按位运算符规则来
即:00000010 2 & 3 =2;
~-2 :11111110 -> 00000001 ~-2 = 1
1.单分支:
if(条件表达式){
执行代码块;(可以有多条语句.)
}
2.双分支
if(条件表达式){
执行代码块;(可以有多条语句.)
}else {
执行代码块2;}
3.嵌套分支
太简单实在懒得写
switch(表达式){
case 常量1: 语句块1; break;
case 常量2: 语句块2; break;}
break:也可以不加看你需求
1.表达式数据类型必须与case后常量类型一致
,或者是可以自动转换的
2.switch(表达式)中表达式的返回值必须是:(byte,short,int,char,enum[枚举],String)
3.case句中的值必须是常量而不能是变量
4.default子句是可选的,当没有匹配的case时,执行default
如果没有default 子句,又没有匹配任何常量.,则没有输出
5.break语句用来在执行完一个case分支后使程序跳出switch语句块
如果没有写break,程序会顺序执行到switch结尾
1.循环条件是返回一个布尔值的表达式
2. for( ;循环判断条件; )中的初始化和变量迭代可以写到其它地方,但是两边的分号不能省略。
循环初始值可以有多条初始化语句,但要求类型一样,并且中间用逗号隔开
3.循环变量迭代也可以有多条变量迭代语句,中间用逗号隔开。
while(循环条件){ //循环条件为true就做
循环体(语句);
循环变量迭代;}
循环变量初始化;
do{
循环体(语句);
循环变量迭代;
}while(循环条件)//循环条件为true就做
先执行、后判断、至少会执行一次
太简单懒得写
切记:
写程序之前构思好每一层循环的作用、不要复杂化,灵活使用判断语句来修改某一层的条件从而达到需求,做不出来很可能是没有划分好每一层的作用。
Random random = new Random();
int number = random.nextInt(10);//使用 Random 生成一个从 0 到 10 的随机数(不包含 10)
double a = Math.random();//可以取一个大于等于0.0小于1.0的值
while(n != 97){
n = (int)(Math.random()*100);
time++;
}
System.out.println(time);//算算多少次能随机出个97
break可以指定退出哪一层
,如果没有指定break,默认退出最近的循环体。仅仅是退出一层
continue用于结束本次循环,继续下次循环,它并不会直接终止掉循环
return用于跳出所在方法
,如果是在主方法中使用return会直接结束进程
①:动态初始化:
数据类型 数组名[ ] = new 数据类型[大小]
②:静态初始化:
数据类型 数组名[ ] = {元素值,元素值…}
int a[] = new int[5];//创建了一个数组,名字a,存放5个int
double[] hens = {3,5,1,3.4,2,50};//静态初始化
1.数组是多个相同类型数据的组合,实现对这些数据的统一管理
2.数组中的元素可以是任何数据类型,包括基本类型和引用类型,但是不能混用
数组创建后,如果没有赋值有默认值0, short 0, byte 0, long 0, float 0.0,double 0.0, char \u0000nt boolean false,String null
3.使用数组的步骤 ①.声明数组并开辟空间 ②.给数组各个元素赋值 ③.使用数组
4.数组的下标是从0开始的
5.数组下标必须在指定范围内使用,否则报 : 下标越界异常,比如int [] arr=new int[5]; 则有效下标为 0-4
6.数组属引用类型,数组型数据是对象(obiect)
1.基本数据类型赋值,这个值就是具体的数据,而且相互不影响
int n1 = 2; int n2 = n1;
2.数组在默认情况下是引用传递,赋的值是地址
int n1 = 10; int n2 = n1; n2 = 80;//n1==10;n2==80; 使用具体数据覆盖变量
int[] arr1 = {1,2,3}; int[] arr2 = arr1; arr2[0] = 10;//此时arr1[0]会受到影响
//此时是将arr1的首地址赋值给arr2。如此arr2也可以指向arr1的地址
1.数组反转:找规律for循环来替换值
2.使用java工具类java.util.Collections
中的自带方法Collections.reverse()
以下是java.util.Collections.reverse()
方法的声明
public static void reverse(List<?> list)
3.静态数组的扩容可以通过创建一个新的数组,将静态数组指针指向新数组。
字符串比较用xxx.equals(xxx);
==的作用: 基本类型:比较值是否相等 引用类型:比较内存地址值是否相等 .equals的作用: 引用类型:默认情况下,比较内存地址值是否相等。可以按照需求逻辑,重写对象的equals方法。
定义形式:int[ ][ ];
**①动态初始化:**类型[ ][ ] 数组名 = new 类型[大小][大小];
类: (由某个事物的共性(特性:属性、行为)提取出来作为数据类型)
int : Java提供的数据类型
猫、xx类:自定义数据类型
类与类之间无法主动import,直接在其中一个类创建另一个类的对象,这样会自动导入
对象:(对象就是类的实例化)
指某个具体的实例
例:int类对象可以为 100、200等等。狗类对象可以为某某家的田园犬等等
Cat cat1 = new Cat();//创建一只猫 赋值给cat1
cat1.name = "小白"; cat1.age = 3; cat1.color = "白色"
Cat cat2 = new Cat();//以此类推,调用时也使用.cat1.name\cat1.age....
class Cat{
String name;//名字
int age;//年龄
String color;//颜色
}
个人总结:
类是抽象的,代表一类事物。对象是具体的,实际的,代表一个具体事物。
类是对象的模板、对象是类的一个个体、对应一个实例
当我们需要增加属性的时候可以直接在类里面增加,在main函数里面赋值调用。这其实也就是面向对象的精髓
cat2 = cat1; 这种情况下类似于指针,同样是指向了cat1的堆当中的数据地址
字符串会放到方法区中的常量池:堆当中放的是常量池里的地址,常量池里面放的才是真正的字符串数据
①:成员变量 = 属性 = field
②:属性是类的一个组成部分,一般是基本数据类型,也可能是引用类型(对象,数组)。比如我们前面定义猫类的int age就是属性
①:属性的定义语法同变量,示例:访问修饰符
属性类名 属性名
访问修饰符:控制属性的访问范围
有四种: public proctected 默认 private
②:属性的定义类型可以为任意类型,包含基本类型或引用类型
③:属性即使不赋值也会有默认值,规则和数组一致
一个对象被实例化之后就不为null了
Cat cat; cat = new Cat();//分两步,先声明再创建
Cat cat = new Cat();
cat.age = 10;
Cat cat1 = cat;//此时类似于c语言指针 cat1指向cat
cat.age = 80;//当cat.age发生改变
System.out.println(cat1.age);//输出结果同样影响 cat1
Java内存的结构分析:
①:栈:一般存放基本数据类型(局部变量)
②:堆:存放对象(Cat cat,数组等)
③:方法区:常量池(常量,比如字符串),类加载信息
④:示意图 [Cat(name,age,price)]
访问修饰符(作用是控制方法使用的范围)
如果不写为默认访问。有四种: public proctected 默认 private
①:一个方法最多有一个返回值
若想返回两个值如下操作:
class AA{
public int[] getSumAndSub(int n1, int n,2){//public之后采用的返回类型是数组 int[]
int [] res = new int[2];
res[0] = n1 + n2;//给数组赋值
res[1] = n1 - n2;
return res;//返回数组这样便可以一个方法返回多个值}}
public 返回数据类型 方法名(参数列表…){
方法体语句;
return 返回值;
}
class AA(){
public void hi(){}}
AA aa = new AA();
new AA().hi();
1.一个方法可以有0个参数,也可以有多个参数,中间用逗号隔开,比如 getSum(int n1,int n2)
2.参数类型可以为任意类型,包含基本类型或引用类型,比如 printArr(intl][] map)
3.调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型 的参数 [getSum]
4.方法定义时的参数称为形式参数,简称形参;方法调用时的参数称为实际参数,简称实参,实参和形参的类型要一致或兼容、个数、顺序必须一致!
5.方法体输出、变量、运算、分支、循环、方法调用,但里里面写完成功能的具体的语句,可以为输入面不能再定义方法!即: 方法不能嵌套定义
PS:引用函数进入方法时,若将函数设为NULL,并不影响主函数当中引用函数的变量。
因为传入方法的时候修改的是p = null 等于断开p指向的主函数当中的p.age。但并不修改其中的值
形参和实参并不意味他们是同样的,形参实际上是方法临时再栈当中开辟的一块新空间,实参不过是和形参拥有相同的地址,但并不意味着他们是连体的,当形参设置为NULL的时候,实参同样还是指向原先的数据
java方法传参机制:
方法的形参的传递机制:
如果参数是基本数据类型,此时实参赋给形参的是实参真实存储的数据值
如果参数是引用数据类型,此时实参赋给形参的是实参存储数据的地址值
1.执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
2.方法的局部变量是独立的,不会相互影响,比如n变量
3.如果方法中使用的是引用类型变量(比如数组),就会共享该引用类型的数据.
4.递归必须向退出递归的条件逼近否则就是无限递归,出现StackOverflowError,死龟了:)
5.当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕。
优点:减轻的起名字、记名字的麻烦
一个方面名对应不同形参有不同处理结果,也利于接口
细节:
①:方法名一定要一样
②:列表:必须不同(参数类型或个数顺序、至少有一样不同、参数名无要求)
③:返回类型:无要求// 主要是形参决定是否重载、返回类型无所谓的
java允许将同一个类中多个同名同功能但参数个数不同
的方法、封装成一个方法
比如:创建一个求 3个整数和、4个整数和、5个整数和的方法。此时只是参数不同但功能相同,我们不需要依次采用重载。而是使用可变参数,如上图所示。
int...
:表示 可以接收多个数据类型。 nums可以当作数组来看, nums.length表示接收到的参数个数.
然后使用for循环可以依次调用,传进去的值会变成nums【】数组 随便调用;**
注意事项
①:可变参数的实参可以是0个或者任意多个
。
②:可变参数的实参可以为数组
在main函数中创建一个数组,调用的时候直接s.sun(arr);
③:可变参数的本质就是数组
④:可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数放在最后
⑤:一个形参列表中只能出现一个可变参数 不能同时放两个可变参数String… str, int… a
1.在java编程中,主要的变量就是属性(成员变量)和局部变量
1.局部变量
局部变量一般是指在成员方法中定义的变量
2.我们说的局部变量一般是指在成员方法中定义的变量。 就是方法里面创建的变量
class Cat{
int age = 10;// 此处为全局变量,它的作用域在整个类里面都可以用
public void cry(){
int n = 10; String name = "java";//左边这两个变量就是局部变量,他们只存在于cat这个方法之内
}}
n
和 name
的作用域在 cry方法
中 ; age
在整个 Cat类
里面都可以使用
3.java中作用域的分类
全局变量: 也就是属性,作用域为整个类体 Cat类: cry eat 等方法使用属性
局部变量: 也就是除了属性之外的其他变量,作用域为定义它的代码块中!
代码块:简单理解为class Cat{... 代码块 ...}
属性(成员方法):Class类当中的 cry方法、或者在Class类却不再方法当中创建的变量
4.全局变量可以不赋值,直接使用,因为有默认值,局部变量必须赋值后,才能使用,因为没有默认值。
1.属性和局部变量可以重名,访问时遵循就近原则。
2.在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名。[举例]
3.属性生命周期较长,伴随着对象的创建而创建,伴随着对象的死亡而死亡。局部变生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而死亡量,即在一次方法调用过程中。
4.作用域不同
全局变量:可以被本类使用,或其他类使用(通过对象调用)
局部变量:只能在本类中对应的方法中使用
5.修饰符不同:
全局变量/属性可以加修饰符
局部变量不可以加修饰符
访问修饰符:控制属性的访问范围
有四种: public proctected 默认 private
构造方法又叫构造器(constructor),是类的一种特殊的方法,它的主要作用是
完成对新对象的初始化
【修饰符】 方法名(形参列表){
方法体;}
①:构造器的修饰符可以默认 也可以是public proctected private
②:构造器没有返回值
③:方法名 和 类名字必须一样
④:参数列表 和 成员方法一样规则
⑤:构造器的调用系统完成
** **:创建对象的时候,系统会自动的调用该类的构造器完成对对象的初始化
此时当我们new一个对象,直接通过构造器来指定名字和年龄。就非常的方便,不需要创建完之后再去一个个赋值
构造系统同样可以重载,构造系统同样可以重载,构造系统同样可以重载,构造系统同样可以重载,构造系统同样可以重载,构造系统同样可以重载,构造系统同样可以重载,构造系统同样可以重载,构造系统同样可以重载,构造系统同样可以重载,构造系统同样可以重载,构造系统同样可以重载,构造系统同样可以重载,构造系统同样可以重载
即构造器重载
1.一个类可以定义多个不同的构造器,不需要指定年龄
比如: 我们可以再给Person类定义一个构造器,用来创建对象的时候,只指定人名
2 构造器名和类名要相同
3.构造器没有返回值
4.构造器是完成对象的初始化,并不是创建对象
5.在创建对象时,系统自动的调用该类的构造方法
6.如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器(也叫默认构造器),比如Person(){},使用javap指令反编译看看
7.一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器,除非显式的定义一下。
对象创建流程分析(面试题
)
class Person{
int age = 90;
String name;
Person(String n,int a){
name = n; age = a;}}
Person p = new Person("小倩",20);//p只是一个对象引用、真正的对象再内存堆当中
①:当我们创建对象的时候,第一步在方法区加载Person类.
②:new的时候就在堆当中开辟一个新的空间(分配空间、地址)
③:全局变量在空间中进行默认初始化,int = 0; string = null; 这是初始化的默认值
④:之后再将有赋值的变量进行赋值,比如Int = 90;
⑤:此时,构造器开始执行,将数据赋值给变量、String引用类型就赋值地址、这个地址指向常量池。
注:构造器只是完成对象的初始化!
class Person{
int age;
String name;
Person(String name,int age){
name = name; age = age;}}
Person p = new Person("小倩",20);
此时若将 构造器Person当中的形参改为和属性同名,则输出结果为 null , 0
原因在于此时构造器当中的name、age皆为形参,所以并没有对属性作出修改。
因此、我们可以使用this。
java虚拟机会给每一个对象分配this。
class Person{
int age;
String name;
Person(String name,int age){
this.name = name;
this.age = age;}}//此时this.age指代的是当前对象的属性age
Person p = new Person("小倩",20);
当前对象的属性age:意味着 谁在new当中调用了该类,那就对应了谁的属性。
Person p = new Person(“JAVA”,30); 此时this.name也对应了这个p的属性name
Person p1 = new Person(“JACK”,50);此时this.name就i对应p1的属性name;
谁调用 this 就针对谁
在构造器当中使用this.所显示的数据完全是根据你的创建对象来决定的,你创建p那就是p的数据、创建p1就是p1的数据。而不是说构造器当中用了this. 这个数据就是固定的了。
简单地说:哪个例子调用,就是那个例子的数据
简单地说:哪个例子调用,就是那个例子的数据
简单地说:哪个例子调用,就是那个例子的数据
1.this关键字可以用来访问本类的属性、方法、构造器
2.this用于区分当前类的属性和局部变量
3.访问成员方法的语法: this.方法名(参数列表);
4.访问构造器语法: this(参数列表): 注意只能在构造器中使用;必须放置再第一条
5.this不能在类定义的外部使用,只能在类定义的方法中使用
包的本质 : 实际上就是创建不同的文件夹来保存类文件,画出示意图
1.包的三大作用:
①:区分相同名字的类
②:当类很多的时候、可以很好的管理类
③:控制访问范围
2.包的基本语法
package com.hspedu;
说明:
1.package 关键字,表示打包
2.com.hspedu:表示包名
1.包命名中 例如 com.xiaoqiang .表示下一级目录 可以理解成/
2.当两个包底下都有同名类的时候,第一次在main程序中调用会import包,第二次就会自动在调用语句前加上包名
命名规则:只能包含数字、字母、下划线、小圆点.,但不能用数字开头,不能是关键字或保留字
命名规范:一般是小写字母+小圆点一般是com.公司名.项目名.业务模块名
比如: com.hspedu.oa.model;com.hspedu.oa.controller;
import 包;
import java.util.*: 表示将 java.util 包所有都引入; 最好还是引入具体的类
1. 注意事项
package 的作用是声明当前类所在的包,需要放在class的最上面,一个类中最多只有一句package
import指令 位置放在package的下面,在类定义前面,可以有多句且没有顺序要求。
java提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限 (范围):
1.公开级别:用public 修饰,对外公开
2.受保护级别:用protected修饰,对子类和同一个包中的类公开
3.默认级别:没有修饰符号,向同一个包的类公开.
4私有级别:用private修饰,只有类本身可以访问,不对外公开
1.修饰符可以用来修饰类中的属性,成员方法以及类
2.只有默认的和public才能修饰类!并且遵循上述访问权限的特点
3.成员方法的访问规则和属性完全一样//com.hspedu.modifier :
❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗❗
1.隐藏实现细节
2.可以对数据进行验证,保证安全合理
3.实现步骤
①.属性进行私有化private(不能直接修改属性)
②.提供一个公共的(public)set方法,用于对属性判断并赋值
public void setXxx(类型 参数名)//Xxx 表示某个属性
//加入数据验证的业务逻辑
③.供一个公共的get方法,用于获取属性的值
public XX getXxx0{ //权限判断
return xx;
}
手写setXXX、getXXX效率太低、因此可以使用快捷键;alt + insert
当程序设计有要求的时候可以再set或者get当中去设置判断
注意:当你在封装之后使用普通的构造器、构造器会直接破坏访问修饰符的限制、避开验证直接修改封装好的数据、因此 我们可以在类当中设置好setXXX和getXXX之后。将setXXX、getXXX写在构造器当中
当两个类的属性和方法有很多是相同的
,我们便可以使用继承(代码复用性)
①:代码的复用性提高了
②:代码的扩展性和维护性提高了
1.继承的基本原理:
继承可以解决代码复用,让我们的编程更加靠近人类思维.当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends
来声明继承父类即可。画出继承的示意图:
可以说D是B的子类、也可以说D是A的子类
2.继承的基本语法
class 子类 extend 父类{
}
①.子类就会自动拥有父类定义的属性和方法
②.父类又叫超类、基类
③.子类又叫派生类
3.继承使用细节
①:子类继承了所有的属性和方法,非私有的属性和方法可以直接访问,但是私有属性不能在子类直接访问,要通过公共方法去访问
例:在子类 调用 public get方法 得到 父类 当中私有的属性和方法是可以访问的
②:子类必须调用父类的构造器,完成父类的初始化 (构造器不能继承)
个人理解:由于子类会用到父类当中的属性,所以在初始化子类的时候,也自动将父类的属性初始化,使得在子类调用公开的方法和属性的时候能够直接使用。
事实上在使用子类构造器的时候,隐藏了一句Super();它来调用了了父类的构造器
③:当创建子类对象时,不管使用子类的哪个构造器,不管是用几次,默认情况下每次一调用子类构造器都会去调用父类的无参构造器;如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过
父类没有提供无参构造器,只提供了有参构造器,此时默认的无参构造器被覆盖。
我们在子类中调用的时候必须主动的在子类构造器的第一句加上super(父类构造器参数);
④:如果希望指定去调用父类的某个构造器,则显式的调用一下:super(参数列表)
根据super当中不同的参数来指定调用父类的某一个构造器
⑤:super在使用时,需要放在构造器第一行,如果有this 那可以在下一个方法(不带this的方法)的前面
⑥:super() 和 this() 都只能放在构造器第一行,因此!这两个方法不能共存在一个构造器
⑦:java所有类都是Object类的子类
⑧:父类构造器的调用不限于直接父类,将一直往上追溯到Object类。子类构造器运行前都会追溯到最高一层的父类,然后从上往下一层层执行构造器
⑨:子类最多只能继承一个父类(直接继承),即java中是**单继承机制
**
若希望 A类 能够继承 B类和C类,则可以A先继承B B再继承C;
⑩:不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
也就是 父类当中必须要有子类的某种特性 例如 Cat extends Animal
3.继承的本质分析
在设计好一系列继承类之后,当你运行子类。它会将从Object往后的每一个父类进行初始化。并且给里面的变量分配空间。
假如每个类都出现了同名的变量,例如图中的name,此时遵循以下逻辑
:
①:首先看子类是否有该属性
②:如果子类有这个属性,并且可以访问,则返回信息
③:如果子类没有这个属性,就看父类有没有这个属性(如果父类拥有并且可以访问就返回信息)
④:如果父类也没有这个属性,那就继续按照③的规则继续向上查找。
假如父类当中的属性被私有了、则可以通过public get去得到值
假设父类当中的属性被私有了、子类在调用的时候发现没有权限
、此时,即便在爷爷类当中出现了同样的属性,也不会去访问了。只会报错
4.继承代码运行流程理解
执行顺序
C类 this(“hello”); 在本类中寻找可以用的方法 public C(String name) 进入public C(String name) 触发super(“hahah”) 返回到父类B 触发B类 public(String name) B类方法中隐藏 super() 进入A类 触发super(); Objuet无输出 我是A类
返回B类 “hahaha我是B类的有参构造” “我是C类的有参构造” “我是C类的无参构造”
有点类似于递归,进入一个方法之后。要把方法做完才能返回到上一层
super代表父类的引用、用于访问父类的属性、方法、构造器
// com.hepedu.super _包下 Super01.java
1.访问父类的属性,但不能访问父类的private属性
2.访问父类的方法,不能访问父类的private方法
3.访问父类的构造器(这点前面用过):super(参数列表);只能放在构造器的第一句,只能出现一句!
1.调用父类构造体的好处(分工明确,父类属性由父类初始化,子类属性有子类初始化)
2.当子类中有和父类中的成员(属性和方法)重名时,为了访问父类成员,必须通过super。如果没有重名,使用super、this、直接访问是一样的效果
cal( ) 等价于 this.cal( ); 等价于 super( );
但是super是直接从父类开始查找,如果父类没有就报错
3.super的访问不限于直接父类,如果爷爷类有同名成员,也可以直接用super访问爷爷类
如果多个基类当中都有同名成员、那就按照**就近原则
**
个人总结:方法覆盖的好处在于,当父类当中的方法无法展示子类当中的某个具体特征的时候,可以用相同命名的方法来满足子类的需求。
方法覆盖(重写):就是子类有一个方法,和父类的某个方法的名称、返回类型、参数
一样那么我们就说子类的这个方法覆盖了父类的方法
注意事项:
2.其实也就是 数据类型为引用类型的时候,子类和父类的引用类型之间可能也存在继承关系。
例如: String 就是 Object的子类。 这种情况下也算是构成了重写、覆盖。
如果 在父类当中的数据类型是子类当中的数据类型的子类。报错
3.例如:父类:public 子类: private
报错
因为子类缩小了父类的访问权限、如果反过来的话则不会报错
1.多态本质和含义
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承的基础上
重载和重写
本身就是多态的体现
重要的几句话:
①:一个对象的编译类型和运行类型可以不一致
例: Animal animal = new Dog(); animal编译类型是Animal;运行类型是Dog;
②:编译类型在定义对象时,就确定了,不能改变
③:运行类型是可以变化的
④:编译类型看定义时 = 号 的左边, 运行类型看 = 号的 右边
注意:看左边的时候如果调用父类方法必须要被子类重写,因为运行时要看子类,不重写会报错
class Animal{
public Cry(){
System.out.println("动物在叫...");
}}
class Dog extends Animal{
public cry(){
System.out.println("狗狗在叫...");
}
class Cat extends Animal{
public cry(){
System.out.println("猫猫在叫...");
}
//此时在main当中调用
//向上转型
Animal animal = new Dog();
animal.cry();//狗狗叫
Animal animal = new Cat();
animal.cry();//猫猫叫
//向下转型
Cat cat = (Cat) animal;
这就是向上转型,因为cry这个方法在后期更新了、优化了。为了能够保持使用新版本的cry(); 所以我们选择向上转型
animal = new Dog或者是 new Cat的时候。本质上面是告诉animal应该去引用哪一个堆。右边就是不同的堆
1.多态的前提条件: 两个对象(类)存在继承关系
2.多态的向上转型:
①.本质:父类的引用指向了子类的对象就称之为:向上转型
②.语法:父类类型 引用名 = new 子类类型
③.特点::编译类型看左边,运行类型看右边。
可以调用父类中的所有成员(需遵守访问权限
) Animal animal = new Cat(); animal可以调用父类所有成员
不能调用子类中特有成员;最终运行效果看子类的具体实现!
子类当中存在的成员、但是父类当中不存在的成员无法调用。
因为在编译阶段,能调用哪些成员是由编译类型来决定。
编译阶段
:直接从编译类型当中去找方法来运行
运行阶段
:由Java来运行、所以会先从指向的Cat()来看,Cat()里存在该成员直接使用,不行就找父类,再不行就爷爷类。
注:以上内容只是再方法中是如此、属性没有重写,谁调用就是谁的堆里的值
程序的运行是需要先通过编译的,所以父类没有办法调用子类的独特方法。
所以真正运行之后我们所看到的其实都是先看子类有没有,如果是子类独有的压根连编译都过不去。
Object obj = new Cat();这样也可以
3.多态的向下转型:
①. 语法: 子类类型 引用名 = (子类类型)父类引用
②. 只能强转父类的引用,不能强转父类的对象
③. 要求父类的引用必须指向的是当前目标类型的对象
④.当向下转型可以调用子类类型中所有的成员
Animal animal = new Cat();
Cat cat = (Cat) animal;
向下转型的前提是,原本Animal animal = new Cat(); animal就指向Cat这个运行类型。
4.多态注意事项:
①:属性没有重写之说、属性的值取决于你的编译类型
②:instancdOf比较操作符,用于判断对象的运行类型
是否为XX类型或XX类型的子类型
BB bb = new BB();// BB继承于AA
Object obj2 = null;
System.out.println(bb instancdOf AA);
System.out.println(obj2 instancdOf AA);
想要向下转型的前提是:父类和子类已经进行过向上转型了。
发生过 Animal animal = new Cat();
1.java的动态绑定机制当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
2.当调用对象属性时,没有动态绑定机制,哪里声明,那里使用
无视黄色部分写出答案
当进入到A类return getl() + 10;的时候究竟是调用A类的还是B类的呢?
答案是根据对象的运行类型来决定,题目当中运行类型为B 所以要运行B类当中的getl();
a.sum() = 30; a.sum1() = 20;
期前依然是 设置父类构造器、子类继承然后加一个属性。引用类型用父类方便向上转型调用多态数组、之后再根据要求向上转型不同的运行类型。然后调用构造器。
当两个子类teacher和student都拥有独特的方法时。我们采取判断。循环判断数组中的对象是teacher的子类还是student的子类、然后根据不同的情况向下转型、从而可以调用子类当中的独特方法
快捷语句:(Student)persons[i]).study()
;//直接向下转型然后调用子类独特方法。
在返回类型是父类的方法中传入子类形参
U盘文件 包salary 供参考
== 是一个比较运算符
①:= =:既可以判断基本类型,又可以判断引用类型
②:= =:如果判断基本类型
,判断的是值是否相等
例:int i = 10, double d = 10.0;
③:= =:如果判断引用类型
,判断的是地址是否相等
,即判定是不是同一个对象
④:比较当中出现基本类型和引用类型时,自动选择比较值
基本类型以数组的形式通通为引用类型
class B{}
class A extends B{}
A a = new A();
A b = a;
A c = b;
System.out.println(a == c);//true
System.out.println(b == c);//true
B obj = a;
System.out.println(obj == c);//true
a指向了A的方法堆、b=a,使得b也指向a;c也指向b指向的a;地址一样所以都是true
即便类型不同,obj和a也是指向同一个对象的对象空间
ctrl + B 查看jdk源码
2.equals:是Object类当中的方法,只能判断引用类型。
3.默认判断的是地址是否相等,子类中往往重写该方法,用于判断内容是否相等 例:Integer,String会自动重写为判断内容、如果是自己new一个引用类型、一样还是比较地址
所以你可以重写自己创建的引用类型的equals方法
经测试、就算只有一个类把equals重写成Object也是可以的
4.A.equals(B); this指A。 B.equals(A); this指B。方法里的this指的是调用 该方法的对象,跟递归没关系
5.引用类型当中的属性如果是String或者Integer。当调用equals来比较这些属性的时候,也当作是String引用类型来比较,如下:
class Person{
public String name;
}
Person p1 = new Person();Person p2 = new Person();
System.out.println(p1.name.equals(p2));
//p.name 是 String类型 只有 p1 才是 Person类型
返回该对象的哈希码值 为了提高哈希表的性能
hashcode并不是地址,是地址的映射。通过某种方式转换地址体现出来的数值是哈希码值
总结:
1.提高具有哈希结构的容器的效率!
2.两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
3.两个引用,如果指向的是不同对象,则哈希值是不一样的
4.哈希值主要根据地址号来的!, 不能完全将哈希值等价于地址。
5.案例演示[HashCode javal: obj.hashCode() [测试: A obj1 = new A(); A obj2 = new A0; Aobj3 = obj1]
6.后面在集合,中hashCode 如果需要的话也会重写
Object当中的toString():
1.getClass().getName() 类的全类名(包名+类名)
2.Integer.toHexString(hashCode())将对象的hashCode值转成16进制
3.重写toString
当你需要打印对象或者被拼接对象的时候,都会自动调用对象toString
一般toString都会被重写,用来输出对象的属性值alt + insert
或者是 右键 Generate
当然了也可以自己设计内容。
4.当你直接输出一个对象时,toString方法会默认被调用:
System.out.println(monster); 这时候就会默认调用moster.toString(); 所以可以在类里面重写
finalize就是一个垃圾回收站,它会来清空已经没有用的对象。
重点:
1.bmw空了之后。car对象没有用了,finalize会被调用来清理垃圾。但并不是一出现垃圾,finalize就马上调用,它拥有自己的算法gc。因此你可以使用System.gc()来主动调用垃圾回收器。但是你使用System.gc()也不一定能够马上调用finalize。
2.即便调用来了System.gc();也是先输出下面的System.out。它并不会在这个System.out之前停留下来等待finalize运行完。它触发完之后就走了
1.断点调试能够一步一步查看源码执行该过程、从而发现错误所在
2.重点提示:在断点调试过程中、是运行状态、是以对象的运行类型来执行的
A extends B;B b = new A(); b.xx( );
断点调试介绍:
1.断点调试是指在程序的某一行设置一个断点,调试时,程序运行到这一行就会停住然后你可以一步一步往下调试,调试过程中可以看各个变量当前的值,出错的话,调试到出错的代码行即显示错误,停下。进行分析从而找到这个Bug
2.断点调试是程序员必须掌握的技能
3.断点调试也能帮助我们查看java底层源代码的执行过程,提高程序员的Java水平
断点调试快捷键:
F7:跳入方法内(跳入);F8:逐行执行代码(跳过);shift + F8:跳出方法 F9(resume,执行到下个断点)、断点可以动态增加
鼠标在代码前的行数点击左键设置断点
*debug一般无法进入jdk源码查看、可以使用 alt + shift + F7
进入源码。
或者 Setting Build,Execution,Deployment Debugger Stepping 把
Do not step into the classes 中的 java.*,javax.取消勾选。就可以进入源码了
static本身就被所有对象共享、所以没有重写的意义
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。
1.静态变量在类加载的时候就已经生成
2.静态变量最核心的一点是,它被所有对象共享
静态变量存在再JVM中的哪里呢?
JDK8之后:静态变量(类变量) 储存在堆当中
JDK8之前:静态变量的类信息存放在方法区,方法区中还有一块静态域用来存放静态变量
定义语法
访问修饰符 static 数据类型 变量名; static 访问修饰符 数据类型 变量名;
如何访问变量
类名.类变量名
或者 对象名.类变量名
静态变量的访问修饰符的访问权限和范围 和 普通属性是一样的
1.当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):
2.类变量与实例变量 (普通属性) 区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的
3.加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
4.实例变量不能通过 类名.类变量名 方式访问
5.类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了。
7.类变量的生命周期是随类的加载开始,随着类消亡而销毁。
定义语法
访问修饰符 static 数据返回类型 方法( ){ }名 static 访问修饰符 数据返回类型 方法( ){ }名
当我们希望不创建实例、也可以调用某个方法(即当作工具来使用);
此时我们把方法做成静态方法非常合适
例:Math类、Arrays类、Collections集合类看下源码
静态方法只能被继承,不能被重写。 非静态方法也不能被静态方法重写
1.类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:
类方法中无this的参数,普通方法中隐含着this的参数
2.类方法可以通过类名调用,也可以通过对象名调用
3.普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用
4.类方法中不允许使用和类有关的关键字
,比如this和super。普通方法(成员方法)可以
5.类方法(静态方法)中 只能访问 静态方法 或者 静态变量
6.普通成员方法,既可以访问普通变量(方法),也可以访问静态变量(方法);
7.同一个方法里面直接静态变量的属性名调用,不需要加上类名前缀了
小结:静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员(必须遵守访问权限)
构造体里的代码只有再NEW新建对象的时候才会触发, 如果是直接调用静态方法,直接用类名调用静态方法是不会触发构造器的。
解释main方法的形式:public static void main(String[] args){}
1.main方法是虚拟机调用
2.java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
3.java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
4.该方法接受String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数,接收参数
5.java 执行的程序 参数1 参数2 参数3
main当中的数组String[] args。其实就是用来存储执行的程序后面的参数的
特别提示:
1.在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性
2.但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
1.可以在main中调用非静态成员(方法和属性),前提是创建好该类的对象
2.可以在main中调用静态成员,直接类名.类方法名
3.不能在两个非main类之间调用非静态类
如何在IDEA里面修改 main里面 String[] args 的内容
代码化块又称为初始化块
,属于类中的成员[即 是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过{ }包围起来
但和方法不同,没有方法名没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。
基本语法:
[修饰符]{
代码};
注意:
1.修饰符 可选,要写的话,也只能写 static
2.代码块分为两类,使用static 修饰的叫静态代码块
,没有static修饰的,叫普通代码块/非静态代码块
3.逻辑语句可以为任何逻辑语句 (输入、输出、方法调用、循环、判断等)
4. 号可以写上,也可以省略。
代码块的好处
1.相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
2.场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
public class CodeBlock{
private String name;
private double price;
{//也可以在代码块前面加static
System.out.println("aaa");
System.out.println("bbb");
System.out.println("ccc");
}
public Code(String name){//构造器1
System.out.println("代码块被调用");
this.name = name;
}
public Code(String name,double price){//构造器2
this.name = name;
this.price = price;
}
}
无论我调用哪个构造器创建对象,代码块都会自动调用在构造器的第一句,代码块的优先顺序高于构造器的内容
输出结果不言而喻,aaa\n bbb\n ccc\n 代码块被调用
1.static代码块也叫静态代码块,作用是对类进行初始化,而且它随着类的加载而执行,并且指挥执行一次。
2.如果是普通代码块,就每创建一个对象执行一次(隐式调用),不创建对象普通代码块就不会被调用,普通代码块的调用和类的加载没有关系
3.类在什么时候被加载
①:创建对象实例时(new)
②:创建子类对象实例,父类也会被加载(初始化子类时会先初始化其父类)
③:使用类的静态成员时(静态属性,静态方法)
②:是因为根据继承,子类的构造器里默认会初始化父类的构造器super();,而父类构造器的代码块又优先于构造器,所以父类代码块->父类构造器->子类代码块->子类构造器
请分清楚:实在什么情况下的顺序!!!!!!!!!!!
4.创建一个对象时,在一个类调用顺序时:( ):
①:调用静态代码块和静态属性初始化(注意: 静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
②:调用普通代码块和普通属性的初始化(注意: 普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
③:代码块的优先级顺序:static方法(属性) > 普通代码块(属性) > 构造器
5.在父类和子类的继承关系下,代码块的调用顺序
构造方法(构造器)的最前面隐含了super()和 调用普通代码块。
当出现两个类继承关系的时候: 经过尝试,可以证明调用顺序如下:
先父类静态,子类静态。在到父类普通、父类构造、子类普通、子类构造
public class CodeBlock2 {
public static void main(String[] args) {
BBB bbb = new BBB();}}
//执行顺序1.AAA父类静态代码块 2.BBB子类静态代码块 3.AAA父类普通代码块
// 4.AAA父类构造器 5.BBB子类普通代码块 6.BBB子类构造器
class AAA{
static {System.out.println("AAA父类静态代码块");}
{System.out.println("AAA父类普通代码块");}
public AAA(){System.out.println("AAA父类构造器");}
} class BBB extends AAA{
static {System.out.println("BBB子类静态代码块");}
{System.out.println("BBB子类普通代码块");}
public BBB(){
//super();
System.out.println("BBB子类构造器");}
}
(继承关系)
①.父类的静态代码块和静态属性(优先级一样
,按定义顺序执行)
②.子类的静态代码块和静态属性(优先级一样
,按定义顺序执行)
③.父类的普通代码块和普通属性初始化(优先级一样
,按定义顺序执行)
④.a父类的构造方法
⑤.子类的普通代码块和普通属性初始化(优先级一样
,按定义顺序执行)
⑥.子类的构造方法 // 面试题
6.静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员
7.代码块和静态成员(静态属性和静态方法)都是随着类的运行就已经初始化了,等你开始调用的时候它们早就已经变化结束了、而不是从你调用的时候才开始。
一个对象被实例化之后就不为null了
练习题:
①:初始化Test表示静态和动态成员初始化开始
②:静态:“静态成员sam初始化” 按定义顺序第二个静态输出:“static块被执行”
③:sam被创建已经不为null,不做输出
④:静态结束到普通属性初始化:“sam1成员初始化”
⑤:所有初始化完毕,进出构造器:“Test默认构造函数被调用”
1.静态方法和属性的经典使用
2.设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式 。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思考和摸索
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
单例模式有两种方式:
①:饿汉式
②:懒汉式
步骤如下:
①:构造器私有化 => 放置直接 new
②:类的内部创建对象
③:向外暴露的一个静态公共方法
④:代码实现
单例模式 - 饿汉式:
单例模式 - 饿汉式:对象,通常是重量级的对象。即便不引用方法中的实例、它也会把实例创建好,饿汉式可能造成创建了对象,但是没有用(浪费
),
单例模式 - 懒汉式:
单例模式 - 懒汉式:只有当用户调用方法的时候,才回去判断实例是否为null。然后创建实例,即便在main创建多次对象调用方法,输出内容也相同
饿汉 VS 懒汉
1.二者最主要的区别在于创建对象的时机
不同: 饿汉式是在类加载就创建了对象实例而懒汉式是在使用时才创建。
2.懒汉式存在线程安全问题。(多线程进入方法创建实例、会同时创建只保留最后一个赋值)
3.饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题
4.在我们iavaSE标准类中,java.lang.Runtime就是经典的单例模式
final 可以修饰 类 属性 方法 局部变量
在某些情况下,程序员可能有以下需求,就会使用到final:
1.当不希望类被继承时,可以用final修饰
2.当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰
3.当不希望类的的某个属性的值被修改,可以用final修饰
4.当不希望某个局部变量被修改,可以使用final修饰
总而严重:当你不希望上面四种类型被修改的时候(例:派 = 3.14),你认为你当前设计已经是最终版本时,就可以使用final来保证你的类、属性、方法、局部变量。不会再发生变化
1.final修饰的属性又叫常量,
一般用全大写
XX_XX_XX来命名
2.final修饰的属性在定义时,必须赋初值
,并且以后不能再修改,赋值可以在如下位置之一[选择一个位置赋初值即可) :
①:定义时: 如 public final double TAX_RATE=0.08:
②:在构造器中(类当中创建final变量 、在构造器里面赋值也可以)
③:在代码块中(类当中创建final变量 、在代码块里面赋值也可以)
3.如果final修饰的属性是静态的
,则初始化的位置只能是:
①:定义时
②:在静态代码块 不能在构造器中赋值
(因为静态代码块和静态属性在类加载时就被运行,构造器最后才被执行)
4.final类不能继承,但是可以实例化对象
5.如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
6.一般来说,如果一个类已经时final类,就没必要再将方法修饰成final方法了(反正也不会被重写)
7.final不能修饰构造方法(构造器)
8.final 和 static 往往搭配使用 效率更高。不会导致类的加载,底层编译器做了优化处理
9.包装类(Integer,double,boolean等都是final),String 也是final类
10.final 也可以放在方法形参、但是同样不能被修改
当父类的某些方法,需要声明。但是又不确定该如何实现,可以将其声明为抽象方法(抽象方法必须放置在抽象类、但抽象类不一定要要有抽象方法
)。
抽象类一般来说会被继承、由子类来实现它的抽象方法。差不多就是留着以防万一的意思。
例:比如我需要写小猫、小狗很多动物的类、那我可以先声明一个动物父类、但我还不知道要怎么实现它,所以我可以先设置为抽象类
abstract class Animal{
public abstract void eat();//能够看到抽象方法压根没有方法体,只是声明了一个方法
}
1.用abstract 关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名{};
2.用abstract 关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名(参数列表)/没有方法体
3.抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类
4.抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多
5.抽象类不能被实例化,不能够创建对象
6.抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法
如果你一点思路都没有、只定义一个抽象类不写方法也可以。
也可以在抽象类当中写可实现方法
7.一旦类包含了abstract方法,则这个类必须声明为abstract
8. abstract 只能修饰类和方法
,不能修饰属性和其它的
10.抽象类可以有任意成员【因为抽象类本质还是类】,比如:非抽象方法、构造器、静态属性等
11.抽象方法不能有主体: public abstract void aaa(){ }
【红色为多余部分】
12.如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract 类
要么继承了之后子类也变成抽象类加上 abstract
要么子类实现父类的抽象方法、(是每一个父类中的抽象方法都要实现)
除此之外:父类的非抽象方法
也是可以重写的。
13.抽象方法不能使用private、final、static来修饰、因为这些关键字都适合重写相违背的。
本身抽象方法就是用来重写的,你private、final、static我还怎么写。
private、final、static:都不能被重写
计时函数
:这是以毫秒为单位的计时,你可以按逻辑模1000
数据类型(long) 数据名(start) = System.currentTimeMillis();//开始时间
{ 代码\\\\ }
数据类型(long) 数据名(end) = System.currentTimeMillis();//结束时间
System.out.println(“end - start”);
模板设计模式的核心思想:
①:搭配抽象类和抽象方法、将多个类的代码提取设置为抽象类的公共方法。
②:再将公共方法中每个类独特的操作环节设置为抽象类,在子类继承的时候重写。以达到一调用父类的方法、就可以执行整个代码顺序。提高效率,提高性能。
代码中当方法运行到父类中的job()方法
时,动态绑定机制会根据创建实例的运行类型回到子类当中去调用子类的job()方法
;
接口就是给一些没有实现的方法,封装到一起,在某个类要使用的时候,再根据具体情况把这些方法写出来。所以 在接口中,抽象方法,可以省略abstract关键字
interface 接口名{
//属性
//方法(抽象方法 /默认实现方法 /静态方法)
}
class 类名 implements 接口{
自己属性;
自己方法;
必须实现的接口的抽象方法
}
1.在jdk7.0以前,接口里的所有方法都没有方法体
2.jdk8.0后接口类可以有静态方法,默认方法。也就是说接口中可以有方法的具体实现
1.如果一个类 implements 实现 接口;需要将该接口所有抽象方法都是实现(重写);
2.想要实现接口当中设计默认方法,需要使用default关键字修饰
;
设计静态方法,需要用static关键词修饰
;否则会默认你写的方法是抽象方法;
什么时候使用接口?
三件类都有一个共同的行为名称(比如三个设备的关闭,打开)。
为了方便明白内容,可以将三个类都接入一个接口。然后三个类分别自己写出他们独特的方法。这样方便调用、而且能够实现统一名称的管理。
接口使用模式(接口多态参数)
通常调用的话,就实例化接入接口的类,然后 对象名.接口内的方法名
1.接口不能被实例化
2.接口中所有方法是 public方法,接口中抽象方法,可以不用abstract修饰
3.一个普通类实现接口,就必须将该接口的所有方法都是实现
当你创建一个普通类接入接口、接口中又有很多方法的话。可以快捷键( alt + enter )
选择第一个快捷创建所有方法重写
4.抽象类实现接口,可以不用实现接口方法
5.一个类同时可以实现多个接口,但每个接口的方法都要实现,出了默认和静态方法
例:class pig implements IB,IC,IG,RNG{ }
6.接口中的属性,只能是final的,而且是public static final 修饰符
例:int a = 1; 实际上是 public static final int a = 1;(必须初始化)
7.接口中属性的访问形式:接口名.属性名
8.一个接口不能继承其他的类,但是可以继承多个别的接口
例:interface A extends B,C{ }
9.接口的修饰符 只能是 public 和 默认,这点和类的修饰符是一样的
10.普通类跟接口之间用 implements接入;接口跟接口之间用 extends 继承;
事实上你不用接口来拓展也可以直接在子类编写新功能
子类可以继承父类之后在接入接口:
但是你必须重写IH当中的抽象方法
特殊情况:当接口中的抽象方法和父类方法同名的时候、不需要重写、会调用父类
class AAA extends BBB implements{};
子类继承父类,就会自动用用父类的功能。
如果子类需要拓展功能,也可以通过实现接口的方式拓展。
可以理解 实现接口 是对 java 单继承机制的一种补充。
继承的价值主要在于:解决代码的复用性和可维护性
接口的价值主要在于:设计,设计好各种规范(方法),让其它类去实现这些方法
接口比继承更灵活,继承是满足 is - a的关系,而接口时满足 like - a的关系
继承的新方法设计还需要回到子类里面去编写,用接口里面设计好的方法(意指方法名)方便管理子类
接口在一定程度上实现代码解耦 ;这个确实是这样(低耦合,高内聚)
1.多态参数在#13当中的截图可以看到。Usb usb ,即能够接收手机对象,又能够接收相机对象
只要类接入了接口、就可以当作Usb【接口名】 这个类型来调用
2.接口也可以”向上转型“
这边的向上转型只是为了容易理解,不是真的向上转型。真正的向上转型指的是继承之间的关系。
main(String[] args){
IF if01 = new Monster(); //接口向上转型
AAA a = new BBB();//继承中的向上转型
}
//以上是主函数///
interface IF{}
class Monster implements IF{}
class AAA{}
class BBB extends AAA{}
3.多态数组: 接口类型数组
继承中的多态数组:#第6章 #第16点
跟继承几乎也是一毛一样
main(String[] args){
Usb[] usbs = new Usb[2];
usbs[0] = new Phone();
usbs[1] = new Camera();
}
//以上是主函数///
interface Usb{}
class Phone implements Usb{ }
class Camera implements Usb{ }
usbs[i] instanceof Phone //判断 usb[i] 运行数组是不是 Phone
4.接口存在多态传递
main(String[] args){
IG ig = new Teacher();
IH ih = new Teacher();//IG如果不继承IH是不可以创建这个实例的
}
//以上是主函数///
interface IH{}
interface IG extends IH{}
class Teacher implements IG{}
除此之外 IH还能够继续继承其他接口、等于是你可以无限接口在接口之上,最终只需要类来接入最孙子的接口就好了
如果IG 继承了 IH 接口,而Teacher 类实现了 IG接口。那么,实际上就相当于 Teacher 类也实现了 IH接口。这就是所谓的 接口多态传递现象。
当你的父类和接口出现一个同名变量。在子类方法中调用这个变量,会报错。因为分不清你到底要调用哪个
这种情况下,你可以使用super来调用父类、用类名、变量名
来调用接口。毕竟接口的属性修饰符是:public static final
一个类的内部有完整的嵌套了另一个类结构。被嵌套的类成为内部类(inner class),
嵌套其他类的类成为外部类(outer class)。是我们类的第五大成员:
【属性、方法、构造器、代码块、内部类】
内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含管
内部类是可以无限嵌套的,底层源码有大量的内部类
class Outer{//外部类
class Inner{//内部类
}}
class Other{//外部其他类
}
1.内部类的分类
定义在外部类局部位置上(比如方法内):
①:局部内部类(有类名)
②:匿名内部类(没有类名,重点!!!!!!!!!!!!!!!!!!!!!!!!!!!)
定义在外部类的成员位置上:
①:成员内部类(没有static修饰)
②:静态内部类(使用static修饰)
局部内部类通常需要定义在外部类方法、代码块之中
说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。
1.可以直接访问外部类所有成员,包含私有的。
2.不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量时不能使用修饰符,但是可以使用final修饰。因为局部变量也可以使用final
3.作用域:仅仅在定义它的方法与或代码块中
4.局部内部类 访问 外部类的成员 【访问方式:直接访问】
5.外部类 访问 局部内部类的成员【访问方式:创建对象再访问(必须在作用域内)】
6.外部其他类 不能访问 局部内部类( 因为局部内部类地位是一个局部变量)
7.如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.this.成员) 去访问,由于外部成员不是static所以需要this.如果是static可以不用
究极图解
匿名内部类定义在外部类方法之中、代码块、在mian函数方法形参之中
对象.getclass(); 获取对象的运行类型
说明:匿名内部类是定义在外部类的局部位置,比如方法中。并且没有类名(表面上)
1.匿名内部类的基本语法
new 类或接口(参数列表){
类体
};
1.本质是类
2.内部类
3.该类没有名字(实际上系统是有分配名字的,但是你看不见)
4.同时还是一个对象
1.可以直接访问外部类的所有成员,包含私有的
2.不能添加访问修饰符,因为它的地位就是一个局部变量
3.作用域: 仅仅在定义它的方法或代码块中
4.匿名内部类 访问 外部类成员
5.外部其他类 不能访问 匿名内部类
6.如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.this.成员)
究极图解 new 接口 的匿名内部类
究极图解 new 类 的匿名内部类
匿名内部类的使用:
1.匿名内部类,既有创建对象的特征,也有类的特征。所以有两种调用方式
①:原本在main当中调用方法有两种方式:
1):Person person = new Person (); person.say();
2):new Person ().say();
②:在匿名内部类调用也有两种方式:
①:Person person = new Person (){ 重写的方法 }; person.say();
②:new Person (){ 重写的方法 }.say();
如果匿名内部类当中重写的方法原本是需要输入参数,那就在Say()括号当中输入
2.如果在匿名内部类当中没有重写,那匿名内部类新建的对象还是会调用原来的方法
成员内部类并不需要定义在外部类方法之中
1.可以直接访问外部类的所有成员,包括私有的
2.可以添加任意访问修饰符(public、protected、默认、private),因为他本身就是一个成员
3.无法直接调用成员内部类,但你可以创建一个外部类的方法去创建内部类实例,然后调用内部类:t1( ) 也是写在Outer08这个类当中的,通过调用这个方法访问成员内部类
4.成员内部类 访问 外部类
5.外部类 访问 内部类
6.外部其他类 访问 成员内部类 有三种方法
7.如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则
,如果想访问外部类的成员使用(外部类名.this.成员)去访问
①:外部类.内部类 对象名 = 外部类.new 内部类( )
Outer08.Inner08 inner08
= outer08.new Inner08(); inner08
.say( );
②:在外部类中编写一个方法,可以返回Inner08对象
创建方法:public Inner08 getInner08(){
return new Inner08( );}
Outer08.Inner08 inner08Instance
= outer08.getInner08( );
inner08Instance
.say( );
说明:静态内部类是定义在外部类的成员位置,并且有static修饰
1.可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
2.可以添加任意访问修饰符,因为它的地位就是一个成员
3.作用域:整个类体
4.静态内部类 访问 外部类
5.外部类 访问 静态内部类
6.外部其他类 访问 静态内部类
7.经典就近原则
因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
①:外部类.内部类 对象名 = new 外部类.内部类( );
对象名.say( )
②:在外部类中编写一个方法,可以返回Inner08对象
创建方法:public static Inner10 getInner10(){
return new Inner10( );}
Outer10.Inner10 inner08Instance
= outer10.getInner10( );
inner08Instance
.say( );
至此,JAVA面向对象初、中、高级已经更新完毕,再接再厉!