1.java语言:
静态强类型语言
静态类型 | 动态类型
------- -------
强类型 | 弱类型
动态语言和静态语言:
python - 动态类型
c c++ java - 静态类型
动态语言定义变量的时候不需要定义变量的数据类型 真正的数据类型在运行阶段赋值的时候才会确定
静态语言定义变量的时候需要定义变量的数据类型
强类型语言和弱类型语言:
强类型:定义一个变量的时候就已经确定了变量所在空间的数据类型,不能保存其他类型的数据
弱类型:变量的内存空间任何数据类型都能存放
2.java程序的执行过程:
.java -> javac.exe -> .class -> jvm 运行
编译
编写-> 编译 -> 运行
除了java 和 javac以外 你还接触过哪些工具?
1.jar.exe -> 把javase项目打包成jar包 web项目打包成war包
部署一个web项目,直接把war包放在tomcat的webapps这个文件下下面
2.javadoc.exe -> 如果我的项目按照javadoc的标准来编写注释的话,javadoc就会自动帮你生成api文档
api文档很重要! 后端提供数据 前端调用后端接口来访问数据 中间通过json进行数据交换
3.javap.exe -> .class文件反编译
.class文件交给jvm运行的时候,会变成一条条的指令进行执行
编译时的特性:
1.常量优化机制 : 如果参与运算的两个数值都是字面值常量的话 ,就会在编译阶段运行完毕
2.泛型
3.foreach循环 ; for(User user : List<User>){
}
->
while(List<User>.hasNext()){
List<User>.next()
}
反射:
反射最大的意义,是在程序运行的时候,获取类的信息(类名,类的修饰符,成员变量,成员变量的修饰符,访问控制权限,方法名,参数列表,返回值)
User user = new User();
1.把.class文件 ClassLoader(类加载器) 加载进方法区
怎么进行加载的?
.class文件 包含了这个类的所有信息
根据这些信息 实例化一个 Class类的一个对象
获得Class类的对象方法:
1.类名.class
2.Class.forname(类名)
1.Fields[] -> 成员变量 每一个成员变量 都是 一个 Field 类的实例
class ---
2.Methods[] -> 成员方法 每一个成员方法 都是一个 Method 类的实例
举例:
class Student{
private int age = 10;
public String name = "张三";
public void read(){
}
}
1. Class student = Student.class
2. student.getFields() -> Field[] 里面的每一个Field对象都是一个属性
student.getMethods() -> Method[] 里面的每一个Method都是一个方法
2.在栈内存里面开辟一个空间存放变量user 这块空间的数据类型是User
栈:方法栈
方法栈里面保存的是每一个被调用方法的栈帧,每一个方法调用的时候就会把这个方法的栈帧给压栈,方法执行完毕以后,就会出栈
(栈顶方法永远是正在运行的方法)
3.在堆内存里面 new一个user对象出来,然后将变量user指向这个对象
如果这个方法执行完毕了,user变量没有了,因为栈帧已经出栈了,new User()还在
java的gc机制:
1.垃圾的意义:没有引用指向的对象
2.什么时候触发gc? jvm内存满了就触发,没满就不触发
3.gc机制会调用 Object 超类 的一个方法 finalize()
4.jvisualvm.exe -> 监视JVM的运行情况,从而发现gc,堆内存存在的问题
3.java的变量,数据类型等内容
java的数据类型:
1.基本数据类型
2.引用数据类型
基本数据类型:
1.byte -> 1字节
2.short -> 2字节
3.int -> 4字节
4.long -> 8字节
5.float -> 4字节
6.double -> 8字节
7.char -> 2字节
8.boolean -> 1字节
java中的基本数据类型都是以补码(符号位不变,其他每一位取反后加1)的形式进行保存的
基本数据类型传参:
值传递
引用数据类型:
对象
引用数据类型传参:
地址传递
面向对象(Object oritented programming):
面向对象是一种编程思想,把现实生活中存在的一个个事物通过软件中对象的方式来进行表示
现实中的事物:
1.属性 -> 这个东西是什么 -> 成员变量
2.行为 -> 这个东西可以干什么 -> 成员方法
面向接口编程:
把现实生活中的接口抽象化到软件中表示
接口(规范):屏蔽掉接口双方的差异性
举例:
JDBC:
sun公司定义的一套关于java操作数据库的规范流程,所有的数据库厂商需要实现这个接口来完成驱动的编写,程序员只需要调用接口
即可完成数据库的操作
程序员不用关心数据库厂商到底怎么写的驱动,因为反正实现了JDBC的接口,肯定有实现类!
数据库厂商不用关心程序员到底怎么写的代码,反正你要调用JDBC的接口!
JDBC做了操作数据库的规范,代码里面需要切换一个数据库的话,只需要换一下加载的驱动即可!其他所有JDBC的代码都不用修改!
Class.forname("org.oracle.xxx");
Connecttion connection = DriverManager.getConnection();
PreparedStatement ps = connection.preparedstatement("select * from user");
ResultSet rs = ps.executeQuery();
while(rs.next()){
xxx
}
SSM以及SpringBoot项目,service层和dao层接口:
interface UserService
class UserServiceImpl
1.dao层针对mysql和oracle可以写两个实现类,不破环以前代码的侵入性
2.接口对行为做了规范,在需求分析阶段:
1.设计数据库
2.设计接口
interface UserDao{
void addUser();
void updateUser(User user);
void delUser(int id);
xxx
}
Service层调用的时候,面向的是接口,不需要关注接口具体怎么实现的,反正接口的实现类要实现这个接口,肯定会有这个接口里面的
所有抽象方法
面向对象三特性:
1.封装
封装就是将一个对象内部的属性隐藏起来,对外提供公共的方法进行访问和修改
封装的意义:
1.保证对象内部的安全性,属性不能随意修改
2.可以在修改和访问属性的时候做额外的操作
public void setxxx(int age){
this.age = age;
sout("年龄修改为了xxx")
}
2.继承
继承:
对现实生活中的父子关系进行了抽象化
在子类对象里面开辟一个父类空间,用来保存父类的相关信息
如果要使用某个成员变量或者成员方法的时候,先去子类对象中找有没有,然后才去父类空间中找
this -> 当前对象
super -> 父类空间
重载和重写:
重载:发生在同一个类里面,方法名相同,参数列表不相同,在调用者看来完全是两个不同的方法
重写:发生在父类和子类之间,父类和子类有完全一样的方法(方法名,参数列表完全一致),子类把父类的方法给掩盖起来
子类会默认调用父类的无参构造,但是如果父类没有无参构造,必须在子类的构造方法里面手动调用父类的某一个构造方法
this()和super()为什么必须写在第一行?
因为super()不写在第一行的话,相当于子类都开始执行方法了,父类空间还没生成
3.多态
多态:一个东西的不同表现方式
运行时多态:
父类引用指向子类对象 向上转型
Fu fu = new Zi()
需要满足的条件
1.继承
2.子类重写父类方法
3.父类引用指向子类对象
1.对于成员变量而言,看的是父类的
2.对于成员方法而言,看子类的
3.对于静态而言,看父类的
举例:
花木兰替父从军
编译时多态:重载
多态的意义?
1.可扩展性,因为一个接口/类可能有很多的实现类/子类,使用多态的话就避免使用子类的特有的方法,
当需要切换一个实现类/子类使用的时候,避免修改以前的代码
2.灵活性
字面值:
指的是看见一个数据,就知道这个数据是什么样的数据类型
1.整形字面值:
123 -> int
123l -> long
2.浮点数字面值:
1.1/1.1d -> double
1.1f -> float
3.null字面值:
空对象
4.char字面值:
'a' -> char
5.boolean字面值:
true -> boolean
6.字符串字面值:
"abc" -> string
所有的字面值都是常量,都存放在常量池
String为什么特殊,因为其他所有的引用数据类型都是通过new关键字创建出来的,只有string有字面值常量
String:
当使用到一个字面值常量的时候首先:
1.先去常量池里面找有没有这个对象存在
2.如果有:则直接将这个对象的地址赋值给变量
如果没有:先在常量池里面创建一个string对象,然后将这个对象的地址赋值给变量
String的特点:
1.可以被共享
String a = "abc"
String b = "abc"
a和b指向的是常量池里面的同一个对象
2.String本质就是一个char[]
3.string不可被修改,因为 char[]被 final关键字修饰
String拼接运算符+本质是干什么事:
String a = "a"+new String("b");
1.创建一个临时的stringbuilder对象
2.调用stringbuilder的append方法进行拼接
3.调用stringbuilder的toString方法把stringbuilder变成一个普通的string
数据类型转换:
普通数据类型:
自动类型类型转换:取值范围小的数据转化为取值范围大的数据 (本质就是前面添加0,对原本的值没有改变)
强制类型转换:取值范围大的数据转化为取值范围小的数据 (把前面几个字节砍一刀,砍没了数据可能会改变)
引用数据类型:
自动类型转换:子类类型转化成父类 向上转型
为什么能自动完成?
因为子类一定包含了父类的信息!
强制类型转换:父类类型转化成子类 向下转型
为什么必须强制完成?
因为父类中可能不包含子类的信息!
4.java的数组
静态数组:
三种定义方法
1.数据类型[] 变量名 = new 数据类型[长度]
2.数据类型[] 变量名 = new数据类型[]{数据1,数据2,xxxx}
3.数据类型[] 变量名 = {数据1,数据2,xxxx}
动态数组:
Collection -> 表示的是一个抽象的概念 一个装东西的集合,单列集合
1.set -> 无序,不可重复的集合
实现类:hashSet:底层就用了hashmap
2.list -> 有序,可重复的集合
实现类:1.linkedlist -> 双向循环链表
双向指的是每一个node 有两个属性 next 指向下一个节点 previous 指向上一个节点
循环:最后一个节点的next指向第一个节点
链表的特点:删除和增加元素很快,但是查找了修改很慢(因为地址不是连续的,我要访问到一个元素,必须从头开始遍历)
2.arrayList -> 动态数组
数组的特点:删除和增加元素很慢,但是查找了修改很快(因为数组的地址是连续的,通过索引的计算就可以直接算出元素的物理地址)
数组的地址:
第一个元素的地址
为什么java要把数组设计成只能保存一种数据类型?
因为方便通过 数组地址+索引*元素大小 来计算元素的实际物理地址
ArrayList的本质:
Object[]
虽然Object数组什么类型的数据都能存放,但是当数据保存在Object[]里面的时候,会向上转型,转化成Object类型
但是取出元素的时候,还是Object类型的数据,就必须进行强制类型转换,向下转型成子类类型
泛型:
泛型就是将 数据类型 作为参数 传递给一个类 (参数化类型)
将类型转换的操作 从运行阶段 提前到了编译阶段
ps:java的泛型是伪泛型,编译以后会进行擦除,即把所有的泛型类型变成Object
ArrayList的扩容机制:
通过无参构造创建出的arraylist 长度为 0
第一次添加元素的时候,会把长度变为10
以后每一次到达最大长度的时候会扩容 grow ,将长度变为以前的1.5倍 1+x>>1 1+0.5
Map -> 双列集合
实现类:HashMap
HashMap的结构:
数组+链表(单向的链表) 每个键值对 Node节点的对象来表示
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
为什么要使用hashmap?
因为hashmap结合使用了数组和链表 所以可以保证 CRUD的速度都比较快
HASH函数/散列函数:
将key和数组的index做一个联系,从而很快速的将Node插入到数组里面去
hash(key) = index
hash函数的意义就在于将元素平均分布到数组中保存(保证每个索引存在数据的概率是一样的)
hash冲突:
equals结果为false的两个对象,经过hash函数计算以后,得到了一样的hash值
HashMap保存元素的过程:
1.保存一个键值对的时候,首先把这个键值对构建为一个 静态内部类 Node 的对象
2.计算hash函数 将 key 经过 hashmap 的一个静态方法 int hash(Object key) 来计算这个键值对的 index索引
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16) -> 可以得到结论如下:
1.hashmap可以保存null键 如果键是null的话 得到的索引为0 把这个键值对放到数组的第一个元素的位置
2.hashmap通过调用键对象的hashcode()方法来计算索引值 计算过程是 右移十六位和以前的hashcode做异或(二进制位上相同位0,不相同位1)
3.hashcode()函数是 object超类提供的
为什么重写了equals方法必须重写hashcode()方法?
equals()方法 -> java官方的定义:判断两个对象是否相等
equals相同的两个对象绝对有相同的hashcode
1.但是如果重写了equals,但是没有重写hashcode的话,可能会出现
equals结果为true的两个对象有不同的hash值
2.还会导致hashmap不可用
因为equals的结果为true的话,证明是同一个对象,放在数组的同一个位置
但是如果没有重写hashcode的话,会出现:
class Student{
int age;
public boolean equals(Student student){
return this.age=student.age?true:false;
}
}
Student student1 = new Student();
Student student2 = new Student();
hashmap.put(student1,1)
1.计算student1的hash值 为 1
2.把student1这个键值对放到数组的 第二个位置
hashmap.put(student2,2)
1.计算student2的hash值 为 1
hashmap.get(student2)
3.计算hash值以后,查看数组位置上是否有元素,如果没有元素的话,直接放进去
如果有元素的话,把这个元素的 key拿出来 和这个键值对的key 用equals方法进行比较 如果结果为true 就替换掉以前的值
如果equals的结果为false 在后面添加链表
HashMap的扩容机制:
1.初始长度是16,最大长度是 2的30次方 长度必须是2的N次幂
2.负载因子:
数组最多能保存 长度*负载因子 个元素 默认的负载因子是 0.75
如果负载因子过小的话:会频繁扩容,影响效率
如果负载因子过大的话:链表长度过长,影响效率
3.如果达到数组最大程度的话(长度*负载因子),就会进行扩容,数组长度扩大为以前的两倍,会重新再hash(因为再hash很花时间,所以应该尽量避免hashmap扩容)
4.如果数组长度大于等于64,链表长度大于等于8的时候,数组+链表就会变成红黑树 进一步增加效率
5.Object
Object超类是所有对象的公共父类
Object:
1. public final native Class<?> getClass(); -> 返回一个对象的Class对象
3个方法获得类对象:
1.Class.forname("类的全限定名称")
全限定名称就是 包名.包名.包名.xxx.类名 比如 com.mysql.jdbc.Driver
2.通过类名.class
3.通过对象的 getClass方法
2. public native int hashCode(); -> 将对象的物理地址转化为了整形数字
3. public boolean equals(Object obj) {
return (this == obj);
} -> 1.==和equals的区别 用于判断两个对象是否相等
2.为什么重写equals方法要重写hashcode
1.因为如果不重写hashcode 可能会出现 两个相等的对象 有 不一样的hash值
2.如果不重写的话,hashmap不可用
4. protected native Object clone() throws CloneNotSupportedException; -> 克隆 复制一份当前对象的拷贝
深拷贝和浅拷贝:
浅拷贝:复制出来的对象在新的地址里,但是里面的引用数据类型和以前的对象一样
深拷贝:复制出来的对象和以前的对象没有任何关系
5.public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
} -> 将对象转化成字符串
6.protected void finalize() throws Throwable { } -> 如果你要自定义销毁对象时触发的函数,需要重写这个方法
gc时会调用