1.什么是Java
Java是一门 面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程 。
2.java的特性和优势
1.简单性
2.面向对象
3.可移植性(平台无关性)
4.高性能
5.分布式
6.动态性
7.多线程
8.健壮性
9.安全性
3.Java的三大版本
JavaSE : 标准版(桌面程序,控制台开发) SE是整个java的核心和基础。
JavaME: 嵌入式开发(手机,小家电)
JavaEE: 企业级开发(web,服务器端开发) EE运用的目前最为广泛,它提供了很多企业级应用开发的完整的解决方案。
4.JDK,JRE,JVM之间的关系
JDK:开发库,JRE:运行时库,JVM: Java虚拟机
JRE可以支撑Java程序的运行,JRE包含了JVM(java.exe)以及基本的核心类库(rt.jar)也就是说,如果仅仅是运行java程序,可以不装JDK,装一个JRE就可以了。
JDK可以支持Java程序的开发,包括了编译器(javac.exe),开发工具javadoc.exe、jar.exe、keytool.exe、jconsole.exe)和更多的类库(如tools.jar等)来更好的帮助开发,当然jdk也包含了jre。
下面是Oracle官方的一张图,有兴趣的可以去了解一下
https://docs.oracle.com/javase/7/docs/
5.Java程序运行机制
如上图所示,我们所写的代码也就是 源文件,会先经过 一次编译,也可以理解为预编译,生成了 一个class的字节码文件,这个字节码文件是介于java代码和机器码中间的,然后将这个class文件放入 jvm中,在jvm中经过 装载校验,然后jvm会通过 解释器解释给操作系统也就是计算机听,然后进行执行。
Java的基础语法
6.注释
注释
Java注释:用于解释说明程序的文字
单行注释
格式: // 注释文字
多行注释
格式: /* 注释文字 /
文档注释
格式:/* 注释文字 */
注释是不会进行编译的,但是文档注释可以通过javadoc生成注释文档,我们的jdk文档也是通过生成注释文档的来的,一个好的注释代码的习惯可以有效的提高我们代码的可读性,下面介绍一种idea生成注释文档的方法。
package com.jj.notes;
/**
* @author 作者名
* @version 版本号
* @since 指定需要的最早使用的jdk版本
*
*
*/
public class Test {
/**
* @return
* @param name
* @throws Exception
*/
public String test (String name)throws Exception{
return "test";
}
}
如图所示的代码,在idea里面选择Tools——>Generate JavaDoc
在如上图需要填入以下内容,Output directory填入文档生成输出的路径,点击OK。
-encoding UTF-8 -charset UTF-8
完成之后可以在输出路径看到如下文件,点击index进主页(一般生成了之后会自动弹出主页的)
可以和jdk帮助文档进行对比,如果用心做一个项目,可以考虑维护这种帮助文档,jdk帮助文档如下。
在这里插入图片描述
7.标识符
标识符,通俗易懂就是名字,Java所有的组成部分都需要名字。类名,变量名,方法名等等都被称为标识符
标识符命名规范
所有的标识符都应该以字母A-Z 或者 a-z,美元符$ 或者 下划线_ 开始
首字符之后可以是字母,美元符,下划线或者数字的任何字符的组合。
不能使用关键字作为标识符
标识符是大小写敏感的(意思是比如关键字class,你取个名字叫Class,cLass,CLAss也是可以的,但是一般不建议那么做)
可以使用中文名,但是一般并不建议去使用,更不建议使用拼音命名,取名字最好就是见名知意,增加可读性。
8.基本数据类型
整数类型和浮点类型也可以使用不同的进制进行操作。
public static void main(String[] args) {
//整数:进制 二进制0b 十进制 八进制0 十六进制0x
int i =10;
int i2=0b10;
int i3=010;
int i4=0x12;
System.out.println(i);
System.out.println(i2);
System.out.println(i3);
System.out.println(i4);
//输出结果为 10,2,8,18
}
浮点数使用问题
浮点数是不精确的,有涉及小数的操作,最好完全避免使用浮点数进行比较,尤其是银行业务!原因大家来看下面这段代码
public static void main(String[] args) {
float f=0.1f;
double d=0.1;
System.out.println(f==d);//输出结果为false
float f1=232311131161613131313131f;
float f2=f1+1;
System.out.println(f1==f2);//输出结果为true
}
为什么浮点数不精确,其实这句话本身就不精确,相对精确一点的说法是:我们程序员在程序里写的十进制的小数,计算机内部无法用二进制的小数来精确的表达。
对于二进制小数,小数点右边能表达的值是1/2,1/4,1/8…1/(2^n)
比如我现在来表达一下十进制的0.2
0.01 = 1/4 = 0.25,太大了
0.001 =1/8= 0.125,又太小了
0.0011=1/8+1/16=0.1875,接近0.2了
0.00111 = 1/8 + 1/16 + 1/32 = 0.21875 , 又大了
0.001101 = 1/8+ 1/16 + 1/64 = 0.203125 还是大
0.0011001 = 1/8 + 1/16 + 1/128 = 0.1953125 这结果不错
0.00110011 = 1/8+1/16+1/128+1/256 = 0.19921875 哇这个好逼近啊 决定就是他了
这就是所说的二进制小数无法精确表示十进制的小数,关于小数的精确计算,推荐使用BigDecimal来做精确运算。
类型转换
低——————————————————>高
byte,char,short——>int——>long——>float——>double
虽然float是三十二位,但是小数的优先级高于整数
高到低强制转换,低到高自动转换。
不能对布尔值进行转换
//类型转换
public class Test02 {
int i=128;
byte b=(byte)i;//内存溢出,强制转换,高到低
double d=i;//自动换转 低到高
}
这里为什么要把char也放进来呢,因为char实际上也是一个数字,所有的字符本质上还是数字,如下图代码
public static void main(String[] args) {
char c1='c';
System.out.println(c1);
System.out.println((int)c1);
char c2='国';
System.out.println(c2);
System.out.println((int)c2);
}
char字符有一张Unicode编码表,每一个数字对应了表中的一个字符,所以字符的本质其实还是数字,正常情况下还有一种\u0000的写法,\u代表的是转译的意思
类型转换注意点
public static void main(String[] args) {
//操作数比较大的时候,注意溢出问题
//jdk7新特性,加下划线不影响数字表达
int money=10_0000_0000;
int years=20;
int total=money*years;//直接溢出 输出-1474836480
long total2=money*years;//依然溢出,说明可能在转换以前就已经存在问题
//输出正常,说明计算之前,把表达式都提升成了long类型进行计算,说明前两个计算先将
//两个表达式进行int类型计算,然后再进行提升,但是计算的时候就已经发生了溢出问题
long total3=money*(long)years;
System.out.println(total3);
}
9.变量
变量是什么,不就是变化的量嘛。
Java是一种强类型语言,所以每个变量都必须声明其类型。
Java变量是程序中最基本的存储单元,其要素包括变量名,变量类型和作用域。
type varName [=value]
每个变量都有类型,类型可以是基本类型,也可以是引用类型。
变量名必须是合法的标识符。
变量声明是一句完整的代码,因此每一个声明都必须以分号结束。
public class Test {
int a; //属性:变量;实例对象:从属于对象,如果不自行初始化,会赋值为这个类型的默认值
static double salary=2500;//类变量 static
public static void main(String[] args) {//main方法
int a = 2;//局部变量,必须声明初始值
}
public void test(){ }//其他方法
}
变量的命名规范
所有的变量,方法名,类名:见名知意
类成员变量:首字母小写和驼峰原则:monthSalary
局部变量:首字母小写和驼峰原则
常量:大写字母和下划线:MAX_VALUE
类名:首字母大写和驼峰原则:GoodMan
方法名:首字母小写和驼峰原则:runRun()
10.运算符
运算时类型问题
public static void main(String[] args) {
byte b=20;
short s=20;
int i=20;
long l=20;
System.out.println(b+s+i+l);//只要运算中有long类型,则返回long类型
System.out.println(b+s+i);//只要运算中有int类型(没有long类型),则返回int类型
System.out.println(b+s);//只要运算中没有long和int类型,则返回int类型
}
短路运算
计算机在进行运算的时候如果提前确认了结果,会直接将语句短路,执行下一条,比如boolean flag=(c<4)&&(c++<4);,如果c>4那么结果必为false,那么后面的c++<4将不再执行,直接返回结果为false,所以如果在这种情况做操作需要考虑一下。
位移运算是最有效率的计算方式
在计算机中最快的办法就是位运算,当问你28最快的运算办法是想到28=222*2,相当于把2在二进制中左移三位,因为在二进制中,左移相当于乘2,右移相当于除2,故写为2<<3
字符串连接符的注意点
public static void main(String[] args) {
int a=10;
int b=20;
System.out.println(""+a+b);//这种情况当作了字符串来处理,也就是将a+b粘在一块,输出1020
System.out.println(a+b+"");//这种情况却当作了数字处理,并不是当字符串处理,也不是先相加再变字符串,输出int类型的30
}
11.流程控制
顺序结构
Java的基础结构就是顺序结构,除非特别指明,否则就按照顺序一句一句执行。
顺序结构是最简单的算法结构
选择结构
if单选择结构
if多选择结构
嵌套if结构
switch多选择结构
多选择结构还有一个实现方式就是switch case语句
switch case语句判断一个变量与一系列中某个值是否相等,每一个值称为一个分支。
switch语句的变量类型可以是 byte short int或者char,到JavaSE 7开始,switch还支持字符串String类型
同时case标签必须为字符串常量或字面量
注意:
switch()括号内填入的值如果不是String类型,则刚刚所提到的可填入的变量类型除了String之外都可以写进case里,但如果填入的是一个String类型,那么case所可以匹配的值都必须是String类型
swtich更多时候是用来匹配一个值,但是匹配的值需要加上break; 否则就会出现穿透现象,
比如case a,b,c 如果匹配的值为b 他会从b开始穿透,没有遇到break的情况会从b开始执行到c一直执行到结束为止(如果没有break)
public static void main(String[] args) {
char c='B';
switch (c){
case 'A':
System.out.println(13);
break;
case 'B':
System.out.println(13);
case 'C':
System.out.println(14);
case 12:
System.out.println(15);
}
//输出结果为 13 14 15
}
注意点:如果我在匹配一个String字符串的时候,传入了一个空的字符串,会发生什么事,会报错吗?
public static void main(String[] args) {
String c=null;
switch (c){
case "A":
System.out.println(13);
break;
case "B":
System.out.println(13);
case "C":
System.out.println(14);
case "12":
System.out.println(15);
}
}
会报错,会直接报NullPointerException错误,为什么会报错,我们可以把这个代码生成的class文件进行反编译,可以得到如下代码。
public static void main(String[] args) {
String c = null;
byte var3 = -1;
switch(((String)c).hashCode()) {
case 65:
if (((String)c).equals("A")) {
var3 = 0;
}
break;
case 66:
if (((String)c).equals("B")) {
var3 = 1;
}
break;
case 67:
if (((String)c).equals("C")) {
var3 = 2;
}
break;
case 1569:
if (((String)c).equals("12")) {
var3 = 3;
}
}
switch(var3) {
case 0:
System.out.println(13);
break;
case 1:
System.out.println(13);
case 2:
System.out.println(14);
case 3:
System.out.println(15);
}
}
可以发现,他们去匹配这个字符串的时候,实际上匹配的是hashcode的值,所以将null进行hascode方法的时候自然会报NullPointerException
循环结构
while必须满足条件,条件表达式只要一直为true就一直执行。
dowhile至少执行一次
break强制退出整个循环,continue跳过本次循环,如果有多层循环想一次性跳出,可以在外面的循环语句前定义一个标号,代码如下
public static void main(String[] args) {
ok://标号
for (int i = 0; i < 10; i++) {
System.out.println(i);
for (int i1 = 0; i1 < 5; i1++) {
System.out.println(i1);
break ok;//break这个标号,两层直接跳出,break只能跳出一层循环
}
}
}
增强for循环,可以直接遍历数组。
public static void main(String[] args) {
int[] i={1,2,3,4};
for(int j:i){
System.out.println(j);
}
}
12.方法
Java方法是 语句的集合,它们在一起执行一个功能。比如人是一个类,上厕所是人的一个方法。
设计方法的原则:方法的本意是功能块,就是实现某个功能的语句块的集合。我们设计方法的时候,最好保持方法的原子性,也就是一个方法只完成一个功能,这样利于我们后期的扩展。
return会直接终止方法。
方法调用有两种方式,根据方法是否返回值来选择,当方法有返回值的时候,通常被当作一个值。如果方法返回void,方法调用一定是一条语句。
那么,方法中的参数是引用传递还是值传递呢?
介绍一下形参和实参
形参是指定义函数名和函数体所用的参数。public void test(String a)
实际参数指调用函数填入括号内的参数。test(“字符串”)。两个之间存在拷贝关系。
java的传递方式
Java中实际上都是值传递。无论你放进去的是一个值,还是一个地址,都只是值传递,也都只是拷贝关系。
public class Test01 {
int c=3;
String d="a";
public static void main(String[] args) {
int a=3;
String b="a";
Test01 test01=new Test01();
test01.test(a,b);
System.out.println(a);//输出结果仍为3
System.out.println(b);//输出结果仍为a
test01.test2(test01);
System.out.println(test01.c);//输出结果为4
System.out.println(test01.d);//输出结果为b
}
public void test(int a,String b)
{
a=4;
b="b";
}
public void test2(Test01 test01){
test01.c=4;
test01.d="b";
}
}
test方法里进行了赋值操作,main方法的a,b没有变化,这也说明了这是一个拷贝的数值,并不是直接修改了源数值。他是将入参复制了一份,进行操作的,完全不影响源数值。那么引用呢,test2就是一个传入test的对象引用,输出结果修改值成功了,那是不是java即是值传递,也是引用传递呢,其实还是只是一个值传递,因为他只不过是复制了你得地址位置。你得地址相当于一把钥匙的话,那么复制你得地址相当于是复刻了一把新的钥匙,这不能算是引用传递,你并没有把原本的钥匙给我,所以,值传递和引用传递的区别并不是传递的内容。而是实参到底有没有被复制一份给形参。
还有一个点就是上面的String也是引用类型,为什么没有改变值,正常来说引用类型应该是传入了一个地址的。因为地址的Test类对象地址输出实际上是toString方法的输出,但是String内部重写了toString方法,输出的不会是地址,而会是值,相当于把输出的值传递进了方法里进行拷贝,这也从侧面反映了java确实是值传递,并没有将引用传递。
方法的重载
重载方法就是在一个类中,有相同的函数名称,但形参不同的函数。
方法重载的规则:
方法名称必须相同。
参数列表必须不同(个数不同,类型不同,参数排列顺序不同)。
方法的返回类型可以相同可以相同。
仅仅返回类型不同无法构成方法的重载。
方法名称相同的时候,编译器会根据调用方法的参数个数,参数类型等去逐个匹配,已选择对应方法,如果匹配失败,编译器报错。
13.递归
看代码
public static void main(String[] args) {
System.out.println( f(4));
}
public static int f(int n)
{
if(n==1){
return 1;
}else {
return n*f(n-1);
}
}
这是一个简单的递归,计算的4的阶乘,过程分析如下
14.数组
数组是相同类型数据的有序集合
数组描述的是相同类型的若干个数据,按照一定的先后次顺序排列组合而成的。
其中,每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问他们。
数组的四个特点
数组的长度是确定的,数组一旦被创建,它的大小就不可以改变
数组元素必须是相同类型,不可以出现混合类型。
数组中的元素可以是任何数据类型,包括基本类型和引用类型。
数组变量属于引用类型,数组也可以看成对象,数组中的每个元素相当于该对象的成员变量。
数组本身就是对象,Java中对象是在堆中的,因此数组无论保存原始类型还是其他对象类型,数组对象本身是在堆中的。
数组内存分析
当声明int[] array;的时候,array会被压入栈内,此时array的值是空的,只有当array=new int[5];的时候,因为数组也是一个引用类型,或者说是一个对象,会在堆中开辟一个array数组的空间,这里定义的数组长度为5,一旦定义长度之后不可改变,数组在这个时候会进行默认初始化,也就是int的数组,会赋值给数组每一个元素int类型的默认值0。
下列是初始化的三种方式
public static void main(String[] args) {
//静态初始化:创建+赋值
int[] a={1,2,3,4,5,6,};
//动态初始化:包含默认初始化
// (默认初始化的意思是开辟了一个十空间的数组,数组元素都赋了一个默认值,int类型默认值为0)
int[] b=new int[10];
b[0]=10;
}
二维数组
二维数组就和套娃一样,多维数组就是无限套娃。二维数组就是一个数组里的每一个元素都是一个数组,所以你在int[][]用new初始化的时候可以先不分配元素上的数组的长度,也就是可以int[][] a=new[10][]new还是一样会赋给每个元素初始值,每个元素都是数组,数组是引用类型,初始值为null。
15.类和对象的创建
类是一种抽象的概念,他是对某一类事物整体的一个描述,定义,但是并不能代表一个具体的事物。
而对象就是抽象概念的具体实例。比如人是一个类,而张三是一个具体的人。
构造器
一个类即使什么都不写,它也会存在一个方法,就是无参构造器。通过反编译你也可以看到编译的时候会给类加上无参的
构造器,当然也可以自己显式的定义无参构造器。如果一个类里面定义了有参构造器,那么无参构造器就必须显式定义。
new实例化一个对象其实就是调用了类中的构造器方法。
创建对象内存分析
我们来看下面的代码进行分析
public class Test02 {
public static void main(String[] args) {
Person xiaoming=new Person();//不推荐使用拼音命名,但是为了显示这是一个具体的人
xiaoming.name="小明";
xiaoming.age=3;
}
}
public class Person {
public String name;
public int age;
public void eat(){
System.out.println("吃饭");
}
}
下面是内存分析图,看着图,跟着文字一起走
1.程序加载,首先会加载有main方法的类进方法区,之前说过class相当于是一个模板,加载进方法,以及里面的常量,常量为“小明”,String类型是被final修饰的,在类中出现的字符串都会被作为常量存到该类的常量池中。
2.将main方法压入栈中,进行执行,(代码都是在栈中执行的,每调用一个方法,就会把方法压入栈中进行执行,也就是说根据先进后出的特性,main方法一定是最后出去的)
3.执行第一句 Person xiaoming=new Person(); 因为Person在方法区中未加载,所以会先将Person这个类加载进入方法区,然后在栈中申明了引用变量名 xiaoming,通过new这个关键字,在堆中创建出了xiaoming这个对象的内存空间,xiaoming指向该内存空间的地址,进行初始化,将name赋上null,age赋上0,eat()指向方法区中它对应的模板类Person的eat()。
4.执行第二句xiaoming.name=“小明”。xiaoming对应的地址空间中的name被赋值成小明,而这个小明是在Test02常量池中的,所以name会指向Test02常量池中的小明
5.执行第三句话 xiaoming.age=3,xiaoming对应的地址空间中的age被复制成了3。
静态方法区是和类一起加载的,static的问题后面会详解
16.面向对象三大特性
封装
封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。
说到底就是属性私有,get/set,禁止访问一个对象中的数据,而应该通过提供操作接口来访问。
继承
继承的本质是对某一批类的抽象。比如学生,老师类都是抽象类,他们都是人类,那么人类就是对这一批类的抽象,也就是抽象的抽象。
extends的意思是扩展。子类是父类的继承。
java中只有单继承,没有多继承,所有人类都继承object(特殊)
idea小快捷键,ctrl+h查看继承树
this关键字和super关键字注意点:
super:super只能出现在子类的方法中或者构造方法中,super代表的是父类对象的引用。
this:this代表调用者这个对象,而this在没有继承的情况下也能够使用,而super只能在继承关系的子类中才可以使用。
方法重写(其实也就是多态了):重写只存在方法的重写,和属性无关,属性可以(重名)扩展,但是不是重写。
而且重写只是重写非静态的方法。
如果我创建两个类 A为子类 ,B为父类,就算B b=new A()父类的引用指向了子类,这个在static的情况下,就算new A,如果有调用和父类同名的方法,默认还是会调用B的方法。
但是如果是非静态方法 那么B b=new A()会执行A中重写了父类的方法 。
private私有修饰符修饰之后,和继承都没有什么关系了,私有的就是只有自己才可以用,儿子都不行。
一个子类被加载,它的父类必定会先被加载。
多态
java实现多态需要满足三个条件:继承,重写,向上转型。
继承:需要存在子类和父类
重写:子类重写了父类的方法。
向上转型:需要将子类的引用赋给父类的对象。比如A为子类 B为父类 B b=new A();
此时用b去调用A有重写了B的方法的时候,会使用子类重写的方法。
这种机制使得引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,只有在程序运行的时候才能确定到底引用变量会指向哪个类的实例对象,引用变量发出的方法调用的是哪个类的方法只有程序运行的时候才能够决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
转载:
https://blog.csdn.net/weixin_44734093/article/details/109715246
内容参考B站狂神说Java