对比面向过程,面向对象是两种不同的处理问题的角度
面向过程更注重事情的每一个步骤及顺序,面向对象更注重事情有哪些参与者(对象)、及各自需要做什么
面向过程比较直接高效,而面向对象更易于复用、扩展和维护
封装的意义,在于明确标识出允许外部使用的所有成员函数和数据项
内部细节对外调用透明,外部则无需修改或者关心内部实现
1、Java bean的属性私有,提供Get/Set对外访问,因为属性的赋值或者获取逻辑只能由Java本身决定,而不能由外部胡乱修改
private String name;
public void setName(String name){
this.name = "tuling_" + name;
}
// 该name有自己的命名规则,明显不能由外部直接赋值
2、ORM框架
操作数据库,我们不需要关心链接是如何建立的、Sql是如何执行的,只需要引入MyBatis,调用即可
继承基类的方法,并做出自己的改变和/或扩展
子类共性的方法或者属性直接使用父类的,而不需要自己再定义,只需扩展自己个性化的
基于对象所属类的不同,外部对同一个方法的调用,实际执行的逻辑不同
多态的条件:继承,方法重写,父类引用指向子类对象
父类类型 变量名 = new 子类对象;
变量名.方法名();
无法调用子类特有的功能
Java Development Kit Java工具开发包
Java Runtime Environment Java运行环境
Java Virtual Machine Java虚拟机
==对比的是栈中的值,基本数据类型是变量值,引用类型是堆中内存对象的地址
equals: object中默认也是采用==比较,通常会重写
Object
public boolean equals(Object obj){
return (this == obj);
}
String
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
上述代码可以看出,String类中被复写的equals()方法其实是比较两个字符串的内容
public class StringDemo{
public static void main(Sting[] args){
String str1 = "Hello";
String str2 = new String("Hello");
String str3 = str2; // 引用传递
System.out.println(str1 == str2); // false
System.out.println(str1 == str3); // false
System.out.println(str2 == str3); // true
System.out.println(str1.equals(str2)); // true
System.out.println(str1.equals(str3)); // true
System.out.println(str2.equals(str3)); // true
}
}
最终的
(1)修饰成员变量
(2)修饰局部变量
系统不会为局部变量初始化,局部变量必须由程序员显示参数,因此使用final修饰局部变量时,即可以在定义时自动默认值(后面代码不能对变量再赋值),也可以不指定默认值,而在后面的代码中对final变量赋初始值(仅一次)
public class FinalVal{
final static int a = 0; // 再声明的时候就需要赋值,或者静态代码块赋值
/*
static{
a = 0;
}
*/
final int b = 0; // 再声明的时候就需要赋值,或者代码块中赋值,或者在构造器中赋值
/*
static{
b = 0;
}
*/
public static void main(String[] args){
final int localA; // 局部变量只声明没有初始化,不会报错,与final无关
localA = 0; // 在使用之前一定要赋值
// localA = 1; 但是不允许第二次赋值
}
}
(3)修饰基本类型数据和引用类型数据
public class FinalReferenceTest{
public static void main(String[] args){
final int[] iArr = {1, 2, 3, 4};
iArr[2] = -3; // 合法
iArr = null; // 非法
final Person p = new Person(25);
p.setAge(24); // 合法
p = null; // 非法
}
}
编译之后会生成两个class文件:Test.class +Test1.class
public void Test{
public static void main(String[] args){
}
// 局部final变量a,b
public void test(final int b){
final int = 20;
// 匿名内部类
new Thread(){
public void run(){
System.out.println(a);
System.out.println(b);
}
}.start();
}
}
class OutClass{
private int age = 12;
public void outPrint(final int x){
class InClass{
public void inPrint{
System.out.println(x);
System.out.println(age);
}
}
new InClass().inPrint();
}
}
首先需要指定的一点是:内部类和外部类是处于同一级别的,内部类不会因为定义在方法中就会跟着方法的执行完毕就被销毁
这里就会产生问题:当外部类的方法结束时,局部变量就会被销毁了,但是内部类对象可能还存在(只有没有人再引用它时,才会死亡)。这里就出现了一个矛盾:内部类对象访问了一个不存在的变量。为了解决这个问题,就将局部变量复制了一份作为内部类的成员变量,这样当局部变量死亡后,内部类仍可以访问它,实际访问的是局部变量的"“copy”。这样就好像廷延长了局部变量的生命周期
将局部变量复制为内部类的成员变量时,必须保证这两个变量是一样的,也就是如果我们在内部类中修改了成员变量,方法中的局部变量也得跟着改变,怎么解决问题呢?
就将局部变量没置为final,对它初始化后,我就不让你再去修改这个变量,就保证了内部类的成员变量和方法的局部变量的一致性。这实际上也是一种妥协。使得局部变量与内部类内建立的拷贝保持一致。
String是final修饰的,不可变,每次操作都会产生新的String对象
StringBufffer和StringBuilder都是在原对象上操作
StringBuffer是线程安全的,StringBuilder是线程不安全的
StringBuffer方法都是synchronized修饰的
性能:StringBuilder > StringBuffer > String
场景:经常需要改变字符串内容时使用后面两个
优先使用StringBuilder,多线程使用共享变量时使用StringBuffer
重载:发生在同一个类中,方法名必须相同,参数类型不同,个数不同,顺序不同,方法返回值和访问修饰符可以不同,发生在编译时
重写:发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符大于等于父类;如果父类方法访问修饰符为private,则子类不能重写该方法
public int add(int a, String b);
public String add(int a, String b);
// 编译报错
使用场景:
关注一个事物的本质的时候,用抽象类
关注一个操作的时候,用接口
hashCode()的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()定义在JDK的Object.java中,Java中的任何类都包含有hashCode()函数。散列表存储的是键值对(key-value),它的特点是:能根据"键"快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
以"HashSet如何检查重复"为例子来说明为什么要有hashCode: 对象加入HashSet时,HashSet会先计算对象的hashCode值来判断对象加入的位置,看该位置是否有值,如果没有、HashSet会假设对象没有重复出现。但是如果发现有值,这时会调用equals ()方法来检查两个对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样就大大减少了equals的次数,相应就大大提高了执行速度。
遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体通过get(i)取得某一元素时都需要对list重新进行遍历,性能消耗极大
另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,使用indexOf对list进行遍历,当结果为空时会遍历整个链表
数组+链表实现
jdk8开始链表高度达到8,数组长度超过64,链表转为红黑树,元素以内部类Node节点存在
jdk7采用头插法插入链表,jdk8则采用尾插法插入链表
数组扩容
jdk7:
数据结构:ReentrantLock+Segment+HashEntry,一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构
元素查询:二次hash,第一次hash定位到Segment,第二次hash定位到元素所在的链表的头部
锁:Segment分段锁,Segment继承了ReentrantLock,锁定操作的Segment,其它的Segment不受影响,并发度为segment个数,可以通过构造函数指定,数组扩容不会影响其它的segment
get方法无需加锁,volatile保证
jdk8:
数据结构:synchronized+CAS(乐观锁)+Node+红黑树,Node的val和next都用volatile修饰,保证可见性
查找、替换、赋值操作都使用CAS
锁:锁链表的head节点,不影响其它元素的读写,锁粒度更细,效率更高,扩容时,阻塞所有的读写操作、并发扩容
读操作无锁:
Node的val和next使用volatile修饰,读写程序对该变量互相可见
数组用volatile修饰,保证扩容时读线程感知
Java中的编译器和解释器:
Java中引入了虚拟机的概念,即在机器和编译直接加入一层程序的虚拟的机器,这台虚拟的机器在任何平台上都提供给程序一个共同的接口
编译程序只需要面向虚拟机,生成虚拟机能够理解的代码,然后由解释器来将虚拟机代码转换为特定系统的机器码执行。在Java中,这种虚拟机理解的代码叫做字节码(即扩展名为.class的文件),它不面向任何特定的处理器,只面向虚拟机
每一种平台的解释器是不同的,但是实现的虚拟机是相通的。Java源程序结果编译器编译后变成字节码,字节码有虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器,解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行,这也就是解释了Java的编译与解释并存的特点
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hq4yp5RP-1621048281561)(e:/typora/class.png)]
好处:
Java语言通过字节码的方式,在一定程度上解决了传统解释型语言效率低的问题,同时又保留解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此Java程序无需重新编译便可在多种不同的计算机上运行
JDK自带有三个类加载器:BootstrapClassLoader、ExtClassLoader、AppClassLoader
继承ClassLoader实现自定义类加载器
好处:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cH1MeExo-1621048281565)(E:/typora/exception.png)]
Java中的所有异常都来自顶级父类Throwable
Throwable下有两个子类Error和Exception
Error是抽象无法处理的错误,一旦出现这个错误,则程序将被迫停止运行
Exception不会导致程序停止,又分为两个部分RuntimeException运行时异常和CheckedException检查异常
RuntimeException常常发生在程序运行过程中,会导致程序当前线程执行失败。
CheckedException常常发生在程序编译过程中,会导致程序编译不通过
GC Roots的对象有:
可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯教的机会。对象被系统宣告死亡至少要经历两次标记过程:第一次是经过可达性分析发现没有与GC Roots相连接的引用链,第二次是在由虚拟机自动建立的finalize队列中判断是否需要执行finalize()方法。
当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象"复活“
每个对象只能触发一次finalize()方法
由于finalize()方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐大家使用,建议遗忘它。
回收对象有无用的类(①.这个类的实现对象已被回收,②.这个类的Class对象不能反射调用获取其方法属性,③.这个类ClassLoader对象已被回收)、废弃的字符串
yield()执行后线程直接加入就绪状态,马上释放了CPU的执行权,但是依然保留了CPU的执行资格,所以有可能CPU下次进行线程调度还会让这个线程获取到执行权继续执行
join()执行线程后进入阻塞状态,例如在线程B中调用线程A的join(),则线程B会进入到阻塞队列,直到线程A结束或中断线程
public static void main(String[] args) throws InterruptException{
Thread t1 = new Thread(new Runnable(){
public void run(){
try{
Thread.sleep(3000);
} catch(InterruptException e){
e.printStackTrance();
}
System.out.println("222222222222");
}
});
t1.start();
t1.join();
System.out.println("1111111111111");
}
不是线程安全,应该是内存安全,堆是共享内存,可以被所有线程访问
当多个线程访问一个对象是,如果不用进行例外的同步控制的协调操作,调用这个对象的行为都可以获得正确的结果,就说这个对象是线程安全的
堆是进程和线程共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的全间,局部堆就是用户分配的全间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是用完了要还给操作系统,要不然就是内存泄漏。
在Java中,堆是Java虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建。堆所存在的内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
栈是每个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是线程安全的。操作系统在切换线程的时候会自动切换栈。栈空间不需要在高级语言里面显式的分配和释放。
目前主流操作系统都是多任务的,即多个进程同时运行。为了保证安全,每个进程只能访问分配给自己的内存空间,而不能访问别的进程的,这是由操作系统保障的。
在每个进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域。这就是造成问题的潜在原因。
Thread和Runable的实际是继承关系,没有可比性。无论使用Runable还是Thread,都会new Thread,然后执行run()方法,用法上,如果有复杂的线程操作需求,那就选中继承Thread,如果只是简单的执行一个任务,那就实现Runable
守护线程:为所有非守护线程提供服务的线程;任何一个守护线程都是整个JVM中所有非守护线程的保姆;
守护线程类似于整个进程的一个默默无闻的小喽喽;它的生死无关重要,它却依赖整个进程而运行;哪天其他线程结束了,没有要执行的了,程序就结束了,理都没理守护线程,就把它中断了;
注意:由于守护线程的终止是自身无法控制的,因此千万不要把IO、File等重要操作逻辑分配给它;因为它不靠谱;
守护线程的作用是什么?
举例,GC垃圾回收线程:就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
应用场景:(1)来为其它线程提供服务支持的情况;(2)或者在任何情况下,程序结束时,这个线程必须正常且立刻关闭,就可以作为守护线程来使用;反之,如果一个正在执行某个操作的线程必须要正确地关闭掉否则就会出现不好的后果的话,那么这个钱程就不能是守护线程,而是用户线程。通常都是些关键的事务,比方说,数据库录入或者更新,这些操作都是不能中断的。
thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个llegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
在Daemon线程中产生的新线程也是Daemon的。
守护线程不能用于去访问固有资源,比如读写操作或者计算逻辑。因为它会在任何时候甚至在一个操作的中间发生中断。
Java自带的多线程框架,比如ExecutorService,会将守护线程转换为用户线程,所以如果要使用后台线程就不能使用Java的线程池
每一个Thread对象均含义一个ThreadLocalMap类型的成员变量threadLocals,它存储本线程中所有ThreadLocal对象及其对应的值
ThreadLocalMap由一个个Entry对象构成
Entry继承自WeakReference<ThreadLocal<>>,一个Entry由ThreadLocal对象和Object构成,由此可见,Entry的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回收
当执行set()方法时,ThreadLocal首先会获得当前线程的ThreadLocalMap对象,再以当前ThreadLocal对象为key,将值存储到ThreadLocalMap对象中
get()方法执行过程类似,ThreadLocal首先会获取当前线程的ThreadLocalMap对象再以当前 ThreadLocal对象作为key,获取对象的value
由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性
使用场景:
spring框架在事务开始i时会给当前线程绑定一个Jdbc connection ,在整个事务过程都是使用该线程绑定的connection来执行数据库操作,实现了事务的隔离性。Spring板架里面就是用的ThreadLocal来实现这种隔离
轻量级的开源的J2EE框架。它是一个容器框架,用来装javabean (Java对象),中间层框架(万能胶)可以起一个连接作用,比如说把struts和hibernate粘合在一起运用,可以让我们的企业开发更快、更简洁
Spring是一个轻量级的控制反转( loC)和面向切面(AOP)的容器框架
系统是由许多不同的组件所组成的,每一个组件各负责一块特定功能。除了实现自身核心功能之外,这些组件还经常承担着额外的职责。例如日志、事务管理和安全这样的核心服务经常融入到自身具有核心业务逻辑的组件中去。这些系统服务经常被称为横切关注点,因为它们会跨越系统的多个组件。
当我们需要为分散的对象引入公共行为的时候,OOP则显得无能为力。也就是说,OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。
日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。
在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。
AOP:将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。AOP可以对某个对象或某些对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情
就是一个map(key,value),里面存的是各种对象(在xml里配置的 bean节点、@Repository
、@Service
、@Controller
、@Component
),在项目启动的时候会读取配置文件里面的bean节点,根据全限定类名使用反射创建对象放到map里、扫描到打上上述注解的类还是通过反射创建对象放到map里
这个时候map里就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过DI注入(@Autowired
、@Resource
等注解,xml里bean节点内的ref属性,项目启动的时候会读取xml节点ref属性根据id注入,也会扫描这些注解,根据类型绘制id注入;id就是对象名)
没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。
引入IOC容器之后,对象A与对象B之间失去了直接联系,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。
通过前后的对比,不难看出来:对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是"控制反转"这个名称的由来。
全部对象的控制权全部上缴给"第三方"IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似粘合剂"的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个粘合剂”,对象与对象之间会彼此失去联系,这就是有人把lOC容器比喻成"粘合剂"的由来。
“获得依赖对象的过程被反转了”,控制被反转之后,获得依赖对象的过程有自身管理变为了由IOC容器主动注入,依赖注入是实现IOC的方法,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中
ApplicationContext是BeanFactory的子接口ApplicationContext提供了更完整的功能:
①继承MessageSource,因此支持国际化。
②统一的资源文件访问方式。
③提供在监听器中注册bean的事件。
④同时加载多个配置文件。
⑤载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
执行计划就是SQL的执行查询的顺序,以及如何使用索引查询,返回的结果集的行数
explain select * from A where X = ? and Y = ?;
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有