一、java语言概述
1、java基础架构介绍
java语言是SUN(Stanford University Network,斯坦福大学网络公司 ) 1995年推出的一门高级编程语言。是一种面向Internet的编程语言。Java一开始富有吸引力是因为Java程序可以在Web浏览器中运行。这些Java程序被称为Java小程序(applet)applet使用现代的图形用户界面与Web用户进行交互。 applet内嵌在HTML代码中。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。
Java语言的特点
- 面向对象
两个基本概念:类、对象
三大特性:封装、继承、多态
- 健壮性
Java编写的程序具有多方面的可靠性。Java编译器能够检测许多在其他语言中仅在运行时才能检测出来的问题。提供了一个相对安全的内存管理和访问机制
- 安全性
Java适用于网络/分布式环境。从一开始,Java程序能够防范各种攻击,其中包括:运行时堆栈溢出;破坏自己进程空间之外的内存;未经授权读写文件。
- 跨平台性
通过Java语言编写的应用程序在不同的系统平台上都可以运行,得益于提供的jvm运行环境(java虚拟机)。
- 简单性
java跟C语言相比较,语法更加简练,提供了更为方便的的接口概念。
什么是JDK、JRE、JVM
从简拼上不难看出
JDK(全名:Java Development Kit), Java 语言的软件开发工具包。JDK是整个java开发的核心,包含了JAVA语言提供的一些工具和JAVA基础的类库,现在的jdk版本也已经融合了JAVA的运行环境JRE。
JRE(全名:Java Runtime Environment),Java运行环境。包括Java虚拟机和Java程序所需的核心类库等;如果想要运行一个开发好的Java程序,计算机中只需要安装JRE即可。
JVM(全名:JVM Java Virtual Machine),Java虚拟机。JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译,也就成就了java语言的一次编译多处跨平台运行的特性。
三者大致的关系也可概括如下:
JDK = JRE+开发工具集
JRE = JVM+Jave SE标准类库
Java两种核心机制
Java虚拟机 (Java Virtal Machine)
JVM是一个虚拟的计算机,具有指令集并使用不同的存储区域。负责执行指令,管理数据、内存、寄存器。
对于不同的平台,有不同的虚拟机,只有某平台提供了对应的java虚拟机,java程序才可在此平台运行。
Java虚拟机机制屏蔽了底层运行平台的差别,实现了“一次编译,到处运行”
垃圾收集机制 (Garbage Collection)
Java 语言消除了程序员回收无用内存空间的责任:它提供一种系统级线程跟踪存储空间的分配情况。并在JVM空闲时,检查并释放那些可被释放的存储空间。
垃圾回收在Java程序运行过程中自动进行,程序员无法精确控制和干预。
Java程序还是会出现内存泄漏和内存溢出的问题。
2、java 面向对象
对 Java 语言来说,一切皆是对象。把现实世界中的对象抽象地体现在编程世界中,一个对象代表了某个具体的操作。一个个对象最终组成了完整的程序设计,这些对象可以是独立存在的,也可以是从别的对象继承过来的。对象之间通过相互作用传递信息,实现程序开发。
Java 是面向对象的编程语言,对象就是面向对象程序设计的核心。
- 对象具有属性和行为。
- 对象具有变化的状态。
- 对象具有唯一性。
- 对象都是某个类别的实例。
- 一切皆为对象,真实世界中的所有事物都可以视为对象。
面向对象的三大核心特性
- 可重用性:代码重复使用,减少代码量,提高开发效率(继承、封装和多态)。
- 可扩展性:指新的功能可以很容易地加入到系统中来,便于软件的修改。
- 可管理性:能够将功能与数据结合,方便管理。
Java面向对象这种开发模式之所以使程序设计更加完善和强大,主要是因为面向对象具有继承、封装和多态 3 个核心特性。
继承性
程序中的继承性是指子类拥有父类的全部特征和行为,这是类之间的一种关系。Java 只支持单继承。
使用方法:将多个类的通用属性和方法提取出来,放在它们的父类中,然后只需要在子类中各自定义自己独有的属性和方法,并以继承的形式在父类中获取它们的通用属性和方法即可。
封装性
封装是将代码及其处理的数据绑定在一起的一种编程机制,该机制保证了程序和数据都不受外部干扰且不被误用。封装的目的在于保护信息。
优点如下:
- 保护类中的信息,它可以阻止在外部定义的代码随意访问内部代码和数据。
- 隐藏细节信息,一些不需要程序员修改和使用的信息。比如取款机中的键盘,用户只需要知道按哪个键实现什么操作就可以,至于它内部是如何运行的,用户不需要知道。
- 有助于建立各个系统之间的松耦合关系,提高系统的独立性。当一个系统的实现方式发生变化时,只要它的接口不变,就不会影响其他系统的使用。例如 U 盘,不管里面的存储方式怎么改变,只要 U 盘上的 USB 接口不变,就不会影响用户的正常操作。
- 提高软件的复用率,降低成本。每个系统都是一个相对独立的整体,可以在不同的环境中得到使用。例如,一个 U 盘可以在多台电脑上使用。
Java 语言的基本封装单位是类。由于类的用途是封装复杂性,所以类的内部有隐藏实现复杂性的机制。Java 提供了私有和公有的访问模式,类的公有接口代表外部的用户应该知道或可以知道的每件东西,私有的方法数据只能通过该类的成员代码来访问,这就可以确保不会发生不希望的事情。
多态性
面向对象的多态性,即“一个接口,多个方法”。多态性体现在父类中定义的属性和方法被子类继承后,可以具有不同的属性或表现方式。多态性允许一个接口被多个同类使用,弥补了单继承的不足。也可以看做是我平时写的serveice层代码。
通过父类新建一个子类对象,通常把子类对象赋值给父类的一个变量。
多态核心
简单来说就是事物存在的多种形态
多态前提:要有继承关系,要有方法重写,要有父类引用指向子类对象
父类引用指向子类对象:
Father father = new Son();
示例代码如下:
public class animals {
public void voice(){
System.out.println("动物都可以叫");
}
}
class dog extends animals{
@Override
public void voice() {
System.out.println("汪汪汪");
}
}
class cat extends animals{
@Override
public void voice() {
System.out.println("喵喵喵");
}
}
class text{
public static void main(String[] args) {
//1多态的体现(直接调用)
animals a = new dog();
a.voice();
animals a1 = new cat();
a1.voice();
//2多态的体现(传参方式)
text t = new text();
t.method(new dog());
t.method(new cat());
}
public void method(animals als){
als.voice();
}
}
3、java 语言基础
- String和 StringBuffer、StringBuilder的区别?
它们就是一个变量和常量的关系。StringBuffer对象的内容可以修改(变量);而String对象一但产生后就不可以被修改,重新赋值其实是两个对象(常量)。
String:
a、String创建的对象是不可变的,一旦创建不可改变
b、String类被final修饰,不可以被继承
c、String创建的对象的值存在于常量池,不用的时候不会被销毁
d、StringBuffer运行时间较长
StringBuffer:
a、可预先分配指定长度的内存块建立一个字符串缓冲区
b、StringBuffer创建的对象是可变的
c、StringBuffer创建的对象的值存在于栈区,不用的时候会被销毁
d、StringBuffer运行时间较短
e、线程安全、支持并发操作,适合多线程
StringBuilder:
a、与StringBuffer本质上没有区别,就是去掉了保证线程安全那部分,减少了开销
b、线程不安全,不支持并发操作,适合单线程
使用推荐:
1、如果经常需要对一个字符串进行修改,例如插入、删除等操作,使用StringBuffer要更加适合一些。反之,用String比较安全。
2、StringBuffer是线程安全的,在多线程程序中也可以很方便的进行使用,但是程序的执行效率相对来说就要稍微慢一些。
3、少量操作字符串使用String就够了,如果在程序中需要对字符串频繁修改连接操作,String在操作字符串的时候new了很多次StringBuffer,建议使用StringBuffer,这样性能更高,很多情况下我们操作字符串不需要线程安全,可以用StringBuilder,减小开销。
- int和Integer有什么区别?
int(数据类型):
a、int是基本数据类型
b、int变量不需要实例化
c、int是直接存储数据值
d、int的默认值是0
Integer(对象):
a、Integer是int的包装类
b、Integer变量必须实例化后才能使用
c、Integer实际是对象的引用,指向此new的Integer对象
d、Integer的默认值是null
- java两种数据类型
(1)基本数据类型,分为boolean、byte、int、char、long、short、double、float;
(2)引用数据类型 ,分为数组、类、接口。
Java为每个基本数据类型提供了封装类
基本数据类型: boolean,char,byte,short,int,long,float,double
封装类类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
- 数组(Array)和列表(ArrayList)的区别?什么时候应该使用 Array 而不是 ArrayList?
区别:Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型。
Array 大小是固定的,ArrayList 的大小是动态变化的。
ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
使用:如果想要保存一些在整个程序运行期间都会存在而且不变的数据,我们可以将它们放进一个全局数组Array 里。但是如果我们单纯只是想要以数组的形式保存数据,而不对数据进行增加等操作,只是方便我们进行查找的话,那么,我们就选择ArrayList。而且还有一个地方是必须知道的,就是如果我们需要对元素进行频繁的移动或删除,或者是处理的是超大量的数据,那么,使用ArrayList就真的不是一个好的选择,因为它的效率很低,使用数组进行这样的动作就很麻烦,那么,我们可以考虑选择LinkedList。
LinkedList与ArrayList的区别
a、ArrayList是实现了基于动态数组的数据结构,LinkedList是基于链表的数据结构
b、对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针
c、对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据ArrayList和LinkedList是两个集合类,用于存储一系列的对象引用
ArrayList是一个数组队列,提供了相关的添加、删除、修改、遍历等功能
LinkedList的增加和删除对操作的效率更高,而查找和修改的操作效率较低
以下情况使用ArrayList:
频繁访问列表中的某一个元素
只需要在列表末尾进行添加和删除元素操作
以下情况使用LinkedList:
需要通过循环迭代来访问列表中的某些元素
需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素的操作
- 什么是值传递和引用传递?
一般认为,java中基础类型数据传递都是值传递,java中实例对象的传递是引用传递。
值传递:方法调用时,实际参数把它的值传递给对应的形式参数,形式参数只是用实际参数的值初始化自己的存储单元内容,是两个不同的存储单元,所以方法执行中形式参数值的改变不影响实际参数的值。
引用传递:也称为传地址。方法调用时,实际参数是对象(或者数组),这是实际参数与形式参数指向同一个地址,在方法执行中,对形式参数的操作实际是就是对实际参数的操作,这个结果在方法结束后,被保留了下来,所以方法执行中形式参数的改变将会影响实际参数。
- Java 支持的数据类型有哪些?什么是自动拆装箱?
基本数据类型包括 boolean(布尔型)、float(单精度浮点型)、char(字符型)、byte(字节型)、short(短整型)、int(整型)、long(长整型)和 double (双精度浮点型)共 8 种
自动装箱:将数据类型转换为包装类型
自动拆箱:将包装类型转换为数据类型
关于Java堆栈的理解
堆和栈都是Java用来在RAM中存放数据的地方,堆和栈是内存中两处不一样的地方。
栈(Stack)中存储的数据类型:基本类型、引用类型变量、方法
栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
栈中主要存放一些基本类型的变量(int, short, long, byte, float, double, boolean, char)和对象句柄。
栈有一个很重要的特殊性,就是存在栈中的数据可以共享。
堆(Heap)中存储的数据类型:由new创建的对象和数组
实际上,栈中的变量指向堆内存中的变量,这就是 Java 中的指针!
- 为什么会出现 4.0-3.6=0.40000001 这种现象?
二进制的小数无法精确的表达10进制小数。计算机在计算10进制小数的过程中要先转换为二进制进行计算,这个过程中出现了误差,就像十进制无法精确表达1/3一样二进制也无法精确表达1/10。
如果基本的整数和浮点数精度不能够满足需求, 那么可以使用java.math 包中的两个 很有用的类:Biglnteger 和 BigDecimal 这两个类可以处理包含任意长度数字序列的数值。 Biglnteger类实现了任意精度的整数运算, BigDecimal 实现了任意精度的浮点数运算。
一般如果在进行金额计算,最好采用long型,单位为分。
- java8 的新特性
它支持函数式编程,新的 JavaScript 引擎,新的日期 API,新的Stream API 等。
Lambda 表达式 − Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
Date Time API − 加强对日期与时间的处理。
Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn javascript引擎,它允许我们在JVM上运行特定的javascript应用。
- 符号“==”比较的是什么?
可以比较基本数据类型和引用类型,基本数据类型比较的是数值;在比较引用类型时,除了比较数值外,还要比较引用地址,两者都相等时,结果才是true。
换言之:比较基本数据类型时,比较数值是否相等。比较引用类型时,比较两个对象在内存中的地址是否相同。
- 若对一个类重写、不重写,它的 equals()方法是如何比较的?
重写:比较的是所指向的对象的内容。
不重写:比较的是引用类型的变量所指向的对象的地址
- 方法重写与方法重载的区别
方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。==》不改变躯壳,只改变行为
方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。==》改变躯壳
方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
- Java 里面的 final 关键字是怎么用的?
final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。
a、修饰类当用final修饰一个类时,表明这个类不能被继承。(尽量不要将类设计为final类)
b、使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率,在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。
c、修饰变量修饰变量是final用得最多的地方。对于一个final变量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象。
- 谈谈关于 Synchronized 和 lock锁
加锁是为了维护数据的一致性和完整性。其实就是数据的安全性。在没有上锁的情况下,一个用户跑去把商品买了,而另一个用户也要买,这之间需要有一个先后顺序。有些操作是敏感的,或者可能导致数据的错误性,所以需要锁。
当多个用户并发地存取数据时,在数据库中就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。
保证多用户环境下保证数据库完整性和一致性。多应用于高并发、多线程对同一数据进行更改,导致数据错乱。
Synchronized是内置的java关键字,Lock是一个java类。
Synchronized无法判断是否获取到了锁,Lock可以判断是否获取到了锁。
Synchronized会自动释放锁,Lock必须手动释放锁。
Synchronized线程1获得锁之后阻塞,等待锁的线程2会一直等下去(死等)。Lock不一定会死等。
Synchronized可重入锁、不可中断、非公平锁。Lock是可重入锁、选择是否可中断、可以选择是否公平。
Synchronized适合锁少量的代码同步问题。Lock适合锁大量的同步代码。
synchronized实现原理
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
普通同步方法,锁是当前实例对象
静态同步方法,锁是当前类的class对象
同步方法块,锁是括号里面的对象
synchronized
原始采用的是CPU
悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU
转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU
频繁的上下文切换导致效率很低。
lock实现原理
lock是jdk层面的锁
lock通过双向链表存储等待锁的线程
lock通过cas和自旋的方式等待锁的获取
Lock
用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS
操作。
- 请说明内部类可以引用他包含类的成员吗,如果可以,有没有什么限制吗?
完全可以。如果不是静态内部类,那没有什么限制!
如果你把静态嵌套类当作内部类的一种特例,那在这种情况下不可以访问外部类的普通成员变量,而只能访问外部类中的静态成员。
- 当一个对象被当作参数传递给一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
是值传递。Java 编程语言只有值传递参数。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对象的引用是永远不会改变的。
- 什么是泛型?
泛型的本质是类型参数化或参数化类型,在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型。
Java 在引入泛型之前,表示可变对象,通常使用 Object 来实现,但是在进行类型强制转换时存在安全风险。有了泛型后:
编译期间确定类型,保证类型安全,放的是什么,取的也是什么,不用担心抛出 ClassCastException 异常。
提升可读性,从编码阶段就显式地知道泛型集合、泛型方法等处理的对象类型是什么。
泛型合并了同类型的处理代码提高代码的重用率,增加程序的通用灵活性。
未使用泛型
public static void method1() {
List list = new ArrayList();
List.add(22);
List.add("hncboy");
List.add(new Object());
for (Object o : list) {
System.out.println(o.getClass());
}
}
未使用泛型前,我们对集合可以进行任意类型的 add 操作,遍历结果都被转换成 Object 类型,因为不确定集合里存放的具体类型,输出结果如下所示:
class java.lang.Integer
class java.lang.String
class java.lang.Object
使用泛型
public static void method2() {
List<String> list = new ArrayList();
list.add("22");
list.add("hncboy");
//list.add(new Object()); 报错
for (String s : arrayList) {
System.out.println(s);
}
}
采用泛型之后,创建集合对象可以明确的指定类型,在编译期间就确定了该集合存储的类型,存储其他类型的对象编译器会报错。这时遍历集合就可以直接采用明确的 String 类型输出。
泛型可以定义在类、接口、方法中,分别表示为泛型类、泛型接口、泛型方法。泛型的使用需要先声明,声明通过<符号>的方式,符号可以任意,编译器通过识别尖括号和尖括号内的字母来解析泛型。泛型的类型只能为类,不能为基本数据类型。尖括号的位置也是固定的,只能在类名之后或方法返回值之前。
一般泛型有约定的符号:E 代表 Element,<E> 通常在集合中使用;T 代表 Type,<T >通常用于表示类;K 代表 Key,V 代表 Value,<K, V> 通常用于键值对的表示;? 代表泛型通配符。
泛型的表达式有如下几种:
普通符号 <T>
无边界通配符 <?>
上界通配符 <? extends E> 父类是 E
下界通配符 <? super E> 是 E 的父类
- ”static”关键字是什么意思?Java 中是否可以覆盖(override)一个 private 或者是 static 的方法?
“static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。
Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。
java中也不可以覆盖private的方法,因为private修饰的变量和方法只能在当前类中使用,如果是其他的类继承当前类是不能访问到private变量或方法的,当然也不能覆盖。
- java修饰符
Java 语言提供了很多修饰符,主要分为以下两类:
- 访问修饰符
- 非访问修饰符
修饰符用来定义类、方法或者变量,通常放在语句的最前端。
访问控制修饰符
Java 中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持4种不同的访问权限。
默认的,也称为 default,在同一包内可见,不使用任何修饰符。
私有的,以 private 修饰符指定,在同一类内可见。
公有的,以 public 修饰符指定,对所有类可见。
受保护的,以 protected 修饰符指定,对同一包内的类和所有子类可见。
非访问修饰符
为了实现一些其他的功能,Java 也提供了许多非访问修饰符。
static 修饰符,用来创建类方法和类变量。
final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
abstract 修饰符,用来创建抽象类和抽象方法。
synchronized 和 volatile 修饰符,主要用于线程的编程。
- 列举你所知道的 Object 类的方法并简要说明。
Object类
超类、基类,所有类的直接或间接父类,位于继承树的最顶层。
任何类,如没有书写extends显示继承某个类,都默认直接继承Object类,否则为间接继承。
Object类中所定义的方法,是所有对象都具备的方法。
Object类型可以储存任何对象。
作为参数,可接受任何对象。
作为返回值,可返回任何对象。
Object类的五个常用方法
getClass() 方法
public final Class<?> getClass(){}
返回引用中储存的实际对象类型。
应用:通常用于判断两个引用中实际储存对象类型是否一致。
public class TestStudent{
public static void main(String[] args){
Student s1 = new Student("aaa",20);
Student s2 = new Student("bbb",20);
// 判断s1和s2是不是同一个类型
Class class1 = s1.getClass();
Class class2 = s2.getClass();
if(class1==class2){
System.out.println("s1和s2属于同一个类型");
}else{
System.out.println("s1和s2不属于同一个类型");
}
}
}
// 输出:s1和s2属于同一个类型
hashCode()方法
public int hashCode () {}
返回该对象的哈希码值。
哈希值根据对象的地址或字符串或数组使用hash算法计算出来的int类型的数值。
一般情况下相同对象返回相同哈希码。
public class TestStudent{
public static void main(String[] args){
Student s1 = new Student("aaa",20);
Student s2 = new Student("bbb",20);
// hashCode方法
// 两个输出肯定是不一样的
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
// 这里s3三的输出与s1一样
Student s3 = s1;
System.out.println(s3.hashCode());
}
}
toString()方法
public String toString(){}
返回该对象的符串表示(表现形式)。
可以根据程序需求覆盖该方法,如:展示对象各个属性值。
public class Student{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public AnonymityOuter(){}
public AnonymityOuter(String name, int age) {
super();
this.name = name;
this.age = age;
}
// 在这里重写toString方法() 如果不重写会输出十六进制 可以看到方法的路径
@Override
public String toString(){
return "Student [name="+ name +",age="+ age +"]";
}
}
public class TestStudent{
public static void main(String[] args){
Student s1 = new Student("aaa",20);
Student s2 = new Student("bbb",20);
// toString方法
System.out.println(s1.toString());// Student [name=aaa,age=20]
System.out.println(s2.toString());// Student [name=bbb,age=20]
}
}
equals()方法
public boolean equals(Object obj){}
默认实现为(this == obj),比较两个对象地址是否相同。
可进行覆盖,比较两个对象的内容是否相同。
public class TestStudent{
public static void main(String[] args){
Student s1 = new Student("aaa",20);
Student s2 = new Student("bbb",20);
// equals方法,判断两个对象是否相等
System.out.println(s1.equals(s2));// false
Student s3 = new Student("张三",18);
Student s4 = new Student("张三",18);
System.out.println(s3.equals(s4));// false
/*
这里因为它是在堆中创建的,每个的地址都不一样,所以equals比较出来的都是false
*/
}
}
equals()方法覆盖步骤
比较两个引用是否指向同一个对象。如果是就直接返回true了
判断obj是否为null。
判断两个引用指向的实际对象类型是否一致。
强制类型转换。
依次比较各个属性值是否相同。
public class Student{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public AnonymityOuter(){}
public AnonymityOuter(String name, int age) {
super();
this.name = name;
this.age = age;
}
// 重写equals方法
@Override
public boolean equals(Object obj){
// 1、判断两个对象是否是另一个引用
if(this == obj){
return true;
}
// 2、判断obj是否null
if(obj == null){
return false;
}
// 3、判断是否是同一个类型
//(this.getClass() == obj.getClass()){}
// instanceof 它可以判断对象是否是某种类型
if(obj instanceof Student){
// 4、强制类型转换
Student s = (Student)obj;
// 5、比较 字符串类型比较用equals
if(this.name.equals(s.getName())&&this.age==s.getAge()){
return true;
}
}
return false;
}
}
public class TestStudent{
public static void main(String[] args){
Student s1 = new Student("aaa",20);
Student s2 = new Student("bbb",20);
// equals方法,判断两个对象是否相等
System.out.println(s1.equals(s2));// false
Student s3 = new Student("张三",18);
Student s4 = new Student("张三",18);
System.out.println(s3.equals(s4));// 重写之前false 经过重写为true
}
}
finalize()方法
当对象被判定为垃圾对象时,由JVM自动调用此方法,用以标记垃圾对象,进入回收队列。
垃圾对象:没有有效引用指向此对象时,为垃圾对象。
垃圾回收:由GC销毁垃圾对象,释放数据存储空间。
自动回收机制:JVM的内存耗尽,一次性回收所有垃圾对象。
手动回收机制:使用System.gc():通知JVM执行垃圾回收。
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student(){
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected void finalize() throws Throwable {
System.out.println(this.name+"对象被回收了");
}
}
public class ObjZong {
public static void main(String[] args) {
System.out.println("------------finalize------------");
// Student s6 = new Student("李四",20);
// Student s7 = new Student("王五",21);
// Student s8 = new Student("赵六",22);
new Student("李四",20);
new Student("王五",21);
new Student("赵六",22);
System.gc();
System.out.println("回收");
}
}
/* 输出:
回收
赵六对象被回收了
王五对象被回收了
李四对象被回收了
*/
- @Override这个标签有什么用呢
a、可以给你当作注释用,感觉这个也不能说明什么,注释也没什么用。
b、可以告诉读你代码的人,这是对它父类方法的重写,其实很多代码规范没有为什么,规范就是规范,代码的可读性还是很重要的。
c、编译器可以给你验证@Override下面的方法名称是否是你父类中所有的,如果没有就会报错。
比如当你想要在子类中重写父类的一个方法,但是你把名字打错了,当你写了@Override编译器会提示你,你写的这个方法父类中没有;但是如果你没有写@Override编译器就会觉得这个是你子类中写的新的方法,并不会报错,到时候你debug还是很麻烦的一件事。
- 类和对象的区别
个人理解:类是对象的定义,而对象是类的实例。
定义了一个类。但是我们还无法直接使用类所持有的成员,要想使用就必须在内存上生成该类的副本,这个副本就是对象。
类是对象的抽象,对象是类的具体实例。
类是抽象的,不占用内存,而对象是具体的,占有内存空间。
- Java 抽象类
一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
抽象类无法直接创建对象,只能被子类继承后,创建子类对象。
4、java设计模式
(1)工厂模式
工厂模式是一种创建模式,因为此模式提供了更好的方法来创建对象。
在工厂模式中,我们创建对象而不将创建逻辑暴露给客户端。
示例代码:
首先,我们设计一个接口来表示Shape
public interface Shape {
void draw();
}
然后我们创建实现接口的具体类
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
(2)抽象工厂模式
抽象工厂模式是另一个创建模式。
抽象工厂模式,也称为工厂的工厂,有一个工厂创建其他工厂。
当使用抽象工厂模式时,我们首先使用超级工厂创建工厂,然后使用创建的工厂创建对象。
(3)单例模式
单例模式是一种创建模式。
这种模式只涉及一个单独的类,它负责创建自己的对象。
该类确保只创建单个对象。
这个类提供了一种访问其唯一对象的方法。
示例:
我们的演示类将使用MainWindow类来获取一个MainWindow对象
class MainWindow {
//创建一个MainWindow对象
private static MainWindow instance = new MainWindow();
//创建一个私有方法
private MainWindow(){}
//获取唯一可用的对象
public static MainWindow getInstance(){
return instance;
}
public void showMessage(){
System.out.println("Hello World!");
}
}
public class Main {
public static void main(String[] args) {
//获取唯一可用的对象
MainWindow object = MainWindow.getInstance();
//调用方法
object.showMessage();
}
}
(4)MVC模式
MVC 模式表示模型 - 视图 - 控制器模式。
Model(模型)- 模型表示携带数据的对象。它也可以具有逻辑来更新控制器,如果其数据改变。
View(视图)- 视图表示模型包含的数据的可视化。通常它有UI逻辑。
Controller(控制器) - 控制器引用模型和视图。它控制数据流进入模型对象,并在数据更改时更新视图。它保持视图和模型分开。
(5) 空对象模式
(6)策略模式
5、java 常用 API
(1)math类:
它包含基本的数字运算方法,如对数、指数、平方根和三角函数等,一般数据类型为double(也有int型)。但是它没有构造方法,有static进行修饰(如果类的成员都是静态的则通过类名就可以直接调用)。
math类中的常用方法:
(2)system类:
它包含n个方法和字段,不能被实例化。
System中代表程序所在系统,提供了对应的一些系统属性信息和系统操作。
System作为系统类,在JDK的java.lang包中,可见它也是一种java的核心语言特性。System类的构造器由private修饰,不允许被实例化。因此,类中的方法也都是static修饰的静态方法。
System类内部包含in、out和err三个成员变量,分别代表标准输入流(键盘输入),标准输出流(显示器)和标准错误输出流(显示器)。
(3)Arrays类:
包含用于操作数组的各种方法;
构造方法用private修饰,成员方法用public修饰。
(4)日期类:
构造方法:Date()分配一个Date对象并对其进行初始化以便它表示分配的时间。
Date(long date)分配一个Date对象并将其初始化为标准的基准时间即1970年1月1日 00:00:00GMT起的制定毫秒数。
SimpleDateFormat类:
它是一个具体的类,用以区域设置敏感的方式格式化和解析日期,日期和时间的格式由日期和时间模式的字符串指定,在日期和时间模式字符串中,从“A”到“Z”,以及从‘a’到‘z’的引号的字母被解释为日期或者时间字符串组建的模式字母。
常用模式字母及对应关系如下:
二、MySQL
1、mysql常用函数
(1)数学函数
数学函数主要用于处理数字,包括整型、浮点数等。
(2)字符串函数
字符串函数是MySQL中最常用的一类函数,字符串函数主要用于处理表中的字符串。
(3)日期时间函数
MySQL的日期和时间函数主要用于处理日期时间。
(4)条件判断函数
a、IF(expr,v1,v2)函数
如果表达式expr成立,返回结果v1;否则,返回结果v2。
SELECT IF(1 > 0,'正确','错误')
->正确
b、IFNULL(v1,v2)函数
如果v1的值不为NULL,则返回v1,否则返回v2。
SELECT IFNULL(null,'Hello Word')
->Hello Word
c、CASE
(5)系统信息函数
系统信息函数用来查询MySQL数据库的系统信息。
(6)聚合函数的介绍
聚合函数又叫组函数,通常是对表中的数据进行统计和计算,一般结合分组(group by)来使用,用于统计和计算分组数据。
count(col): 表示求指定列的总行数
max(col): 表示求指定列的最大值
min(col): 表示求指定列的最小值
sum(col): 表示求指定列的和
avg(col): 表示求指定列的平均值
2、sql优化
(1)避免使用select *
实际中可能我们真正需要使用的只有其中一两列。查了很多数据,但是不用,白白浪费了数据库资源、内存或者cpu。
此外,多查出来的数据,通过网络IO传输的过程中,也会增加数据传输的时间;
select * 不会走索引,会出现大量的回表操作,而从导致查询sql的性能很低。
示例:
select name,age from user where id=1;
(2)用union all代替union
我们都知道sql语句使用union关键字后,可以获取排重后的数据。而如果使用union all关键字,可以获取所有数据,包含重复的数据。排重的过程需要遍历、排序和比较,它更耗时,更消耗cpu资源。所以如果能用union all的时候,尽量不用union。
除非是有些特殊的场景,比如union all之后,结果集中出现了重复数据,而业务场景中是不允许产生重复数据的,这时可以使用union。
示例:
(select * from user where id=1)
union all
(select * from user where id=2);
(3)小表驱动大表
小表驱动大表,也就是说用小表的数据集驱动大表的数据集。
假如有order和user两张表,其中order表有10000条数据,而user表有100条数据。时如果想查一下,所有有效的用户下过的订单列表。可以使用in关键字实现:
示例:
select * from order
where user_id in (select id from user where status=1)
(4)批量操作
但众所周知,我们在代码中,每次远程请求数据库,是会消耗一定性能的。而如果我们的代码需要请求多次数据库,才能完成本次业务功能,势必会消耗更多的性能。
示例:
orderMapper.insertBatch(list):
提供一个批量插入数据的方法
insert into order(id,code,user_id)
values(123,'001',100),(124,'002',100),(125,'003',101);
这样只需要远程请求一次数据库,sql性能会得到提升,数据量越多,提升越大。
但需要注意的是,不建议一次批量操作太多的数据,如果数据太多数据库响应也会很慢。批量操作需要把握一个度,建议每批数据尽量控制在500以内。如果数据多于500,则分多批次处理。
(5)高效的分页
有时候,列表页在查询数据时,为了避免一次性返回过多的数据影响接口性能,我们一般会对查询接口做分页处理。
(6)join的表不宜过多
根据阿里巴巴开发者手册的规定,join表的数量不应该超过3个。
如果join太多,mysql在选择索引的时候会非常复杂,很容易选错索引。
(7)选择合理的字段类型
char表示固定字符串类型,该类型的字段存储空间的固定的,会浪费存储空间。
varchar表示变长字符串类型,该类型的字段存储空间会根据实际数据的长度调整,不会浪费存储空间。
(8)建立索引、索引优化
可以使用explain命令,查看mysql的执行计划。
explain select * from `order` where code='002';
3、数据库事务
start transaction;开启事务
commit;提交
rollback;回滚
简而言之:在同时操作多条相关数据的时候,要么同时成功要么同时失败。
事务的四大特性(ACID):
原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
一致性(Consistency)事务前后数据的完整性必须保持一致。
持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
隔离性(Isolation)事务的隔离性是指多个用户并发操作数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。 简单来说: 事务之间互不干扰。
三、redis缓存
1、redis介绍
redis即远程字典服务,基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
(1)redis缓存产品有以下三个特点:
- Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
- Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
- Redis支持数据的备份,即master-slave模式的数据备份。
(2)Redis与其他缓存数据库比较:
(3) redis数据类型和底层数据结构的对应关系:
2、redis常见的三种问题及解决方案
缓存雪崩、 缓存穿透、 缓存击穿
缓存雪崩:
- Redis挂掉了,请求全部走数据库。
- 缓存数据设置的过期时间是相同的,然后刚好这些数据删除了,全部失效了,这个时候全部请求会到数据库。
缓存雪崩如果发生了,很有可能会把我们的数据库搞垮,导致整个服务器瘫痪。
解决方案:
实现Redis的高可用(主从架构+Sentinel 或者Redis Cluster),尽量避免Redis挂掉这种情况发生。
万一Redis真的挂了,我们可以设置本地缓存(ehcache)+限流(hystrix),尽量避免我们的数据库被干掉(起码能保证我们的服务还是能正常工作的)。
在存缓存的时候给过期时间加上一个随机值,这样大幅度的减少缓存同时过期。
缓存穿透:
缓存穿透是指查询一个一定不存在的数据。由于缓存不命中,并目出干容错考虑,如果从数据库查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,失去了缓存的意义。
解决方案:
由于请求的参数是不合法的(每次都请求不存在的参数),于是我们可以使用布隆过滤器(BloomFilter)或者压缩filter提前拦截,不合法就不让这个请求到数据库层。
当我们从数据库找不到的时候,我们也将这个空对象设置到缓存里边去。下次再请求的时候,就可以从缓存里边获取了。这种情况我们一般会将空对象设置一个较短的过期时间。
缓存击穿:
缓存击穿和缓存雪崩类似,也是因为Redis中key过期导致的。只不过缓存击穿是某一个热点的key过期导致的。当有一个热点数据突然过期时,就会导致突然有大量的情况直接落到MySql上,导致MySql直接爆炸!
解决方案:
设置热点Key永不过期
四、敏捷开发
以用户的需求进化为核心,采用迭代、循序渐进的方法进行软件开发。
5种主流开发方法
(1)极限编程
(2) 水晶方法
把开发看做是一系列的协作游戏,而写文档的目标是帮助团队在下一个游戏中取得胜利。水晶方法的工作产品包括用例、风险列表、迭代计划、核心领域模型,以及记录了一些选择结果的设计注释。水晶方法也为这些产品定义了相应的角色。值得注意的是这些文档没有模板,描述也不太规范,但目标清晰,能够满足下次游戏开始的条件。
对于水晶方法论,根据方法论的轻重可以分为透明水晶和橙色水晶等。透明水晶一般适用于轻量级的团队。不管是哪种水晶,都会对团队的角色、团队的工作项和产出、核心实践、支持过程等进行定义。
(3)动态系统开发方法
动态系统开发方法(DSDM)倡导以业务为核心,快速而有效地进行系统开发。可以把DSDM看成一种控制框架,其重点在于快速交付并补充如何应用这些控制的指导原则。
DSDM是一整套的方法论,不仅仅包括软件开发内容和实践,也包括了组织结构、项目管理、估算、工具环境、测试、配置管理、风险管理、重用等各个方面的内容。
(4)精益开发
精益(Lean)管理的思想起源于丰田公司,旨在创造价值的目标下,通过改良流程不断地消除浪费。这种方法现已被广泛用于生产制造管理,对于IT系统建设,精益开发的常用工具模型是价值流模型。
(5)Scrum
Scrum 是一个用于开发和维护复杂产品的框架 ,是一个增量的、迭代的开发过程。在这个框架中,整个开发过程由若干个短的迭代周期组成,一个短的迭代周期称为一个Sprint,每个Sprint的建议长度是2到4周。
在Scrum中,使用产品Backlog来管理产品的需求,产品backlog是一个按照商业价值排序的需求列表,列表条目的体现形式通常为用户故事。Scrum团队总是先开发对客户具有较高价值的需求。在Sprint中,Scrum团队从产品Backlog中挑选最高优先级的需求进行开发。
挑选的需求在Sprint计划会议上经过讨论、分析和估算得到相应的任务列表,我们称它为Sprint backlog。在每个迭代结束时,Scrum团队将递交潜在可交付的产品增量。 Scrum起源于软件开发项目,但它适用于任何复杂的或是创新性的项目。