基础知识
二进制的原码、反码、补码
在计算机底层中,所有的数字都是以二进制的补码存储的。
对于正数:三码相同。
对于负数:
- 原码:直接将该数转成而二进制码,最高位是符号位,0代表正数,1代表负数
- 反码:符号位不变,其他位取反
- 补码:反码+1
Java基础部分
方法的重载与重写
重载:在同一个类中,存在多个重名方法,只要他们的**参数列表(参数个数或者参数类型)**不同即可构成重载。
“两同一不同”:同一个类,同一个方法名;参数列表不同
与其他的比如 权限修饰符、返回值类型、参数变量名等等都没关系,只要能满足“两同一不同”,就能构成方法重载。
重载的调用地址在编译期就绑定了
重写:子类继承父类以后,可以对父类中的非静态方法进行覆盖操作。当子类重写父类中的方法后,用子类对象调用父类和子类同名的方法,执行的是子类中的方法。
重写的规则:
- 重写的方法和被重写的方法 的 方法名和参数列表相同
- 重写的方法的权限修饰符不小于被重写方法的权限修饰符。(父类中的private修饰的方法不能被重写)
- 重写的方法抛出的异常范围不大于被重写的方法抛出的异常范围
- 若被重写的方法的返回类型是void或者是基本数据类型,则重写的方法的返回值类型也必须是void或者基本数据类型
- 若被重写的方法的返回值类型是引用类型,则重写方法的返回值类型是该引用类型或其子类
注意:
父类中的静态方法不能被重写,但是子类和父类中可以存在同名同参数的方法,但是这种形式不是重写,是他俩各自内部都有一个静态方法,两者之间没有关系。
Java的值传递
对于基本数据类型:传递的是该基本类型的值。
对于引用数据类型:传递的是存放该值的地址值。
this关键字
this代表 当前对象 或者 当前正在创建的对象。
当方法的形参名和类的属性名同名的时候,在方法内部必须使用this.变量
来声明当前操作的变量是类的属性,而不是形参。(包括构造器)
还可以使用this关键字调用构造器:
在类的构造器中使用this(形参列表)
来调用其他构造器,但是不能调用自己,且如果要使用的话必须在当前构造器的首行使用,因此如果要使用this(形参列表)
来调用其他构造器的话最多只能调用一个其他构造器,不能调用多个。
super关键字
在子类中,通过super.属性
和super.方法
来调用父类中的属性和方法,通常情况下,super可以省略。
但是当子类和父类中存在同名的属性或方法,在子类中调用父类的属性或方法的话就必须使用super关键字来显式调用父类的属性和方法。
super调用构造器:
在子类的构造器中可以使用super(形参列表)
显式地调用父类构造器,如果使用的话必须是在子类构造器的首行,所以this
和super
只能使用一个,不能同时使用,如果都没有显式调用的话,默认调用的是super()
。
注意:
虽然在子类构造器中默认调用了super()
,但是只创建了一个子类对象,super()
只是加载了父类的结构,让子类看看是否去调用。
封装
封装是指隐藏对象内部的复杂性,只对外提供简单的接口,便于外界调用,提高程序的可扩展性、可维护性。
封装性的体现:
- 将类的属性设置为私有(private),提供getter和setter来获取和设置属性值
- 将方法设置为private
- 单例模式(构造器私有化)
Java中的权限修饰符:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o4ETEX4Q-1616459127524)(C:\Users\zhq\AppData\Roaming\Typora\typora-user-images\image-20210317181633334.png)]
private:只能在同一个类中进行访问。
default(缺省):只能在类内部、同一个包中进行访问。
protected:可以在类内部、同一个包、不同包的子类中访问
public:只要是在同一个工程中都可以访问。
注意:
4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类
修饰类的话,只能使用:缺省、public
继承
继承是为了扩展类的功能,当一个类继承了另外一个类的时候,子类就拥有了父类所有的属性和方法,并且子类还可以扩展出自己的属性和方法。**注意,**上面说的拥有了所有的属性和方法是事实,如果父类中有私有属性和方法(使用private修饰了),实际上来说子类也能拥有,只是因为private修饰的属性和方法只能在本类中访问,子类中没有访问权限而已。
使用继承的好处:
- 减少代码冗余,提高代码的复用性
- 便于程序功能的扩展
- 为实现多态提供前提
多态
理解为一个事物的多种形态。同一个行为,使用不同的实例,而有不同的表现形式。
**对象的多态性:**父类引用指向子类对象(或者子类对象赋给父类引用)
如:Person person = new Student();
多态形成的必要条件:
- 类的继承
- 方法的重写
- 父类引用指向子类对象
Person person = new Student();
**多态的使用:**虚拟方法的调用。使用多态的时候,我们在编译期只能调用父类中声明的方法;但是在运行期实际执行的是子类中重写的方法。也就是:编译看左边,运行看右边。也就是说如果想使用多态,调用子类中的方法的话,必须让子类继承一个类,然后对父类中的方法重写。
**使用的时候注意:**对象的多态性,只适用于方法,不使用于属性。使用多态的时候,先看父类中有没有该方法,如果没有,则编译错误;如果有,则调用子类中的同名方法。
使用多态的时候的转型:
向上转型是自动转型的,向下转型是需要强转的。
-
向下转型的原因?
因为在使用多态的时候,
Person person = new Student();
变量的类型是父类的类型,使用person
只能调用父类中的属性和方法,但是子类中特有的属性和方法实际上是已经加载到内存中了,为了能够使用子类特有的属性和方法,需要将person
向下转型为student
类型。但是在转换的时候,有可能会出现转换异常,所有需要先使用isinstanceof
关键字判断对象是否是某个类的实例,然后再进行强转。
对多态的理解:
-
实现代码的通用性。
-
可以使用Object类中定义的
equals
等方法。//传过来的参数是Object的子类,使用Object的实例接收,用到了多态 Object object = new Man(); public boolean equals(Object obj) { return (this == obj); }
-
抽象类、接口的使用体现了多态。
-
多态是运行期行为。
多态的实现方式:
- 重写
- 接口
- 抽象类和抽象方法
抽象类
- 抽象类不能实例化,只能通过一个非抽象的子类继承该抽象类,然后实例化子类。但是它也有构造器,只是不能直接实例化抽象类而已。
- 抽象类不一定包含抽象方法,但是含有抽象方法的类一定是抽象类。因为抽象方法只有方法声明,没有方法体,必须有一个非抽象的类继承该抽象类,然后对声明的方法做实现。
- 构造器,静态方法不能被声明为抽象方法。
- 抽象类的子类必须实现该抽象类中的抽象方法,除非该子类也声明为抽象类。
接口
- 接口不能被实例化,没有构造方法。
- 接口中的方法全部是抽象方法(默认使用
public abstract
修饰),只有方法声明,没有方法体,且变量都是全局常量,使用public static final
修饰。但是在jdk1.8中,接口中可以有静态方法、默认方法(这两个是有方法体的)。 - 类不是继承接口,而是实现接口。
- 一个类可以实现多个接口,接口可以继承接口,并且可以多继承
- 当一个类实现接口的时候,必须实现接口中的全部方法,除非该类时抽象类
接口相关题目
题目一:找错
interface A{
int x = 0;
}
class B {
int x = 1;
}
public class C extends B implements A {
public void px() {
//错误在这里,这里的x编译器不知道是A的x还是B的x,所以编译会出错
System.out.println(x);
//如果要调用A的x,可以使用 System.out.println(A.x); 因为接口中的常量是静态不可变常量,public static final
//如果要调用B的x,可以使用 System.out.println(super.x);
}
public static void main(String[] args) {
new C().px();
}
}
题目二:找错
错误点:最后的play()方法中 ball = new Ball(“Football”);是对接口Rollable中的ball赋值,但是接口中的变量都是静态全局常量,是不可变的,所以在此赋值的话会编译报错。
接口和抽象类的异同?
相同点:
- 都不能被实例化
- 都包含有抽象方法
不同点:
- 抽象类中的成员变量可以是任何类型的,但是接口中的变量只能是全局常量,也就是使用
public static final
修饰的 - 一个类只能继承一个抽象类,而一个类可以实现多个接口
final、finally、finalize的区别
-
final:
final关键字可以修饰类、属性、方法,分别表示类不能被继承、属性不可变、方法不可被重写(但是可以重载)。
- 修饰类表示该类不能被继承
- 修饰修饰基本数据类型表示该类型是个常量,修饰引用数据类型表示该引用不能指向其他的对象,但是该引用指向的对象的状态是可以改变的。
- 修饰方法表示该方法不能被重写,但是可以被重载
-
finally
finally用在异常处理中,表示无论try中是否发生了异常、catch中是否匹配上异常,finally中的代码都会执行。
-
finalize
finalize()是Object类中的一个方法,在垃圾收集器清理某个没有被引用的对象的时候,会调用该对象的finalize()方法执行清理工作。
==和equals()的区别
==适用于基本数据类型和引用数据类型,equals只适用于引用数据类型
-
==
当==比较的是基本数据类型的时候,比较的就是两个基本数据类型的值是否相同;
当==比较的引用数据类型的时候,比较的是两个对象的内存地址是否相同;
-
equals()
equals()方法是Object类中的方法,被所有类共享。在Object类中,equals方法比较的是两个引用类型的地址值是否相同,但是在一些类(String、Date、Integer等)中,对equals()方法进行了重写,重写之后的功能是判断两个引用类型对应的对象值是否相同。
代码的加载顺序
当实例化一个子类对象的时候,涉及到父、子类静态代码块、非静态代码块、构造器的执行顺序:
代码:
public class Father {
public Father() {
System.out.println("父类构造器");
}
{
System.out.println("父类非静态代码块");
}
static {
System.out.println("父类静态代码块");
}
}
public class Son extends Father {
public Son() {
System.out.println("子类构造器");
}
{
System.out.println("子类非静态代码块");
}
static {
System.out.println("子类静态代码块");
}
}
public class Test {
public static void main(String[] args) {
new Son();
}
}
输出结果:
父类静态代码块
子类静态代码块
父类非静态代码块
父类构造器
子类非静态代码块
子类构造器
总结:由父及子,静态先行
异常
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ko51HcHj-1616459127526)(C:\Users\zhq\AppData\Roaming\Typora\typora-user-images\image-20210318163949853.png)]
异常的处理:
- try-catch-finally:真正地将异常捕获并处理掉了
- throws:在方法的声明上使用,只是将异常自动抛给方法的调用者了,并没有真正处理掉异常
- throw:手动抛出一个异常类的对象,使用在方法体中。
实际开发中两种方式如何选择?
- 如果父类中被重写的方法没有throws,那么子类就不能throws。(因为重写的方法抛出的异常范围不大于被重写方法,被重写的方法都没抛,你也不能抛)
- 执行的方法a()中调用了b(),而b()又调用了c()等等,那么在a中就可以使用try-catch-finally进行捕获,然后处理掉异常
Java高级部分
多线程
程序、进程、线程的理解
-
程序:就是一段静态代码
-
进程:正在运行的程序。
进程是资源分配的基本单位,系统在运行的时候会为每个进程分配不同的内存区域
-
线程:进程可进一步细化为线程,是一个程序的一条执行路径
线程是资源调度和执行的基本单位,每个线程拥有独立的栈和程序计数器,线程的切换开销小。
并行与并发
- 并行:多个cpu同时执行多个任务
- 并发:一个cpu使用时间片的方式同时执行多个任务
###创建多线程的方式
-
继承Thread
-
实现Runnable接口
-
实现Callable接口
**优点:**call有返回值;call可以抛出异常;Callable支持泛型
-
线程池
提前创建好多个线程,放入线程池中,使用的时候直接从线程池中获取线程,使用完毕后再放入池中,可以避免频繁创建销毁线程,实现重复利用。
好处:提高响应速度;降低资源消耗;便于线程管理
前两种方式对比:继承Thread类和实现Runnable接口
相同点:1.都需要重写run()方法,将线程要执行的逻辑写在run()方法体内;
2.启动线程都是调用start()方法
优先使用:实现Runnable接口。
原因: 1.实现接口的方式避免的单继承的局限性
2.实现接口的方式适合处理多线程共享数据的情况
###Thread类常用方法
start()
:启动线程,让处于新建状态的线程变为就绪状态run()
:运行线程。执行该方法的时候代表该线程已经获得cpu资源,处于运行状态currentThread()
:返回执行当前代码的线程getName()
:获得当前线程的名字setName()
:设置当前线程的名字yield()
:释放当前CPU的执行权。即暂停当前执行的线程,去执行其他的线程join()
:等待线程终止。在线程a中执行b.join()
,线程a会进入阻塞状态,一直等到线程b结束后,线程a才结束阻塞状态stop()
:已过时。强制结束当前线程sleep()
:让当前线程睡眠,进入阻塞状态isAlive()
:判断当前线程是否存活
线程的生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TEACiqBG-1616459127528)(C:\Users\zhq\AppData\Roaming\Typora\typora-user-images\image-20210318201244061.png)]
线程的同步机制
- 同步代码块(synchronized)
- 同步方法(synchronized)
- Lock锁
synchronized和lock的对比:
- Lock是显式锁,需要手动开启和关闭锁;synchronized是隐式锁,出了作用域会自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- Lock锁的性能更好
死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了死锁。
注意:出现了死锁以后,不会出现异常,不会出现提示,所有的线程都处于阻塞状态,无法继续
线程的通信
线程通信涉及的三个方法:
wait()
:执行此方法,当前线程进入阻塞状态,并释放锁notify
:执行此方法,会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的notifyAll()
:执行此方法,会唤醒被wait的所有线程
说明:
以上三个方法都必须使用在同步代码块或者同步方法中且调用者必须是同步代码块或同步方法的同步监视器
sleep()和wait()的异同:
相同点:都会让线程进入到阻塞状态
不同点:
-
wait()方法是在Object类中声明的,sleep()是在Thread类中声明的
-
sleep()可以在任何场景下被调用,wait()只能在同步代码块或同步方法中被调用
-
sleep()没有释放锁,wait()方法释放了锁
-
sleep()会自动苏醒,被wait()的线程必须被同一个对象上的notify或者notifyAll唤醒
常用类
String
-
String s1 = “abc” 与 String s2 = new String(“abc”)
String s1 = “abc” ; 是直接赋值,字符串“abc”存在常量池中,s1指向常量池中的地址
String s2 = new String(“abc”); 实际上是创建了两个对象,在常量池中创建了一个对象、在堆中创建了一个对象并指向常量池
因此 s1 == s2 为false;
-
字符串的拼接:
String s1 = "hello"; String s2 = "world"; String s3 = "hello" + "world"; String s4 = s1 + s2; String s5 = s1 + "world"; String s6 = (s1 + s2).intern(); System.out.println(s3 == s4);//false System.out.println(s3 == s5);//false System.out.println(s4 == s5);//false System.out.println(s3 == s6);//true 结论: 常量与常量的拼接结果在常量池。且常量池中不会存在相同内容的常量。 只要其中有一个是变量,结果就在堆中 如果拼接的结果调用intern()方法,返回值就在常量池中
###StringBuffer、StringBuilder、String
String:不可变的字符串
StringBuffer:可变的字符串,线程安全,效率低(但是比String效率高)
StringBuilder:可变字符串,线程不安全,效率高
泛型
泛型提供了一种类型安全的监测机制,能够在编译时检查出非法的类型
如果类B是A的子类,G是具有泛型声明的类或接口,G并不是G的子类型!!
比如,List 并不是 List的子类型
序列化与反序列化
序列化:将内存中的Java对象转换成二进制序列保存在磁盘上或者在网络上传输
serialVersionUID
:表述序列化版本标识符,在反序列化的时候,JVM会把流中传过来的serialVersionUID
与实体类中的serialVersionUID
进行比较,相同就认为是一致的,可以反序列化;否则就抛出版本不一致的异常。
对Serializable接口的认识:
实现了该接口的对象,可以将对象转换成字节,并从字节转换成对象;这意味着序列化机制可以自动补偿操作系统间的差异性.
匿名内部类
匿名内部类就是没有名字的内部类。
好处:简化书写
使用的前提条件:须存在继承或实现的关系的时候
使用匿名内部类重写父类方法:
public class Demo01 {
public static void main(String[] args) {
//匿名内部类重写方法
new Dog(){
@Override
void eat() {
System.out.println("狗在吃东西");
}
}.eat();//调用
}
}
class Animals {
void eat() {
System.out.println("吃东西!");
}
}
class Dog extends Animals{
}
Java 8 新特性
- Lambda表达式:让代码更简洁高效
设计模式
单例模式
模板方法模式
在软件开发的时候,一个算法的实现整体步骤很固定,就可以在父类中写好一部分,然后将易变、不固定的那一部分抽象出来,让子类继承父类然后实现该抽象方法,这就是模板方法模式。【由抽象类和抽象方法(abstract关键字)引出】
代码:
public abstract class Template {
//计算某段代码花费的时间
public void spendTime() {
long start = System.currentTimeMillis();
this.code(); //易变的代码部分,将其抽象抽来,供子类实现
long end = System.currentTimeMillis();
System.out.println("花费的时间:" + (end - start));
}
//抽象方法,供子类实现
public abstract void code();
}
public class Test extends Template {
//实现父类中易变的那部分
@Override
public void code() {
long sum = 1;
for (int i = 1; i < 1000000000; i++) {
sum += i;
}
System.out.println("sum:" + sum);
}
public static void main(String[] args) {
Test test = new Test();
test.spendTime();
}
}
输出:
sum:499999999500000001
花费的时间:327
该设计模式的应用场景:
- 数据库访问的封装
- javaWeb中Servlet的doGet和doPost方法的调用
- Spring中JDBCTemplate
代理模式(重要)
由interface(接口)关键字引出。
代理模式就是为其他对象(代理对象)提供一种代理以控制对这个对象(被代理对象)的访问
- 静态代理
创建一个接口,在接口中声明一个抽象方法,创建一个被代理类,并让该被代理类实现接口并重写接口中的抽象方法;然后创建一个代理类,也实现这个接口,同时在代理类中持有一个被代理类的对象的引用,在代理类实现的方法中调用被代理类的方法,就实现了静态代理。
静态代理的(特点)缺点:一个代理只能为一个类服务,如果多个类需要代理,就需要写多个代理类,比较麻烦,动态代理可以解决这个问题。(代理类和被代理类在编译期间,就确定下来了)
public class NetWorkTest {
public static void main(String[] args) {
Server server = new Server();
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.browse(); //通过代理,让真实的服务器访问网络
}
}
interface NetWork{
//提供一个联网的方法,让代理类和被代理类实现
void browse();
}
//被代理类
class Server implements NetWork{
//实现联网的方法
@Override
public void browse() {
System.out.println("真实的服务器访问网络。。。");
}
}
//代理类,代理Server实现联网
class ProxyServer implements NetWork{
private NetWork netWork;
//使用多态;用network接收server
public ProxyServer(NetWork netWork) {
this.netWork = netWork;
}
public void check() {
System.out.println("联网之前的检查。。");
}
@Override
public void browse() {
check();
//帮助(代理)Server实现联网
netWork.browse();
}
}
输出:可以看到server并没有显式地调用browse()方法,而是使用代理类的方法,进而执行了它的方法。这就是代理模式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0tS1aoBc-1616459127531)(C:\Users\zhq\AppData\Roaming\Typora\typora-user-images\image-20210318002218790.png)]
- 动态代理
核心:运行期动态创建代理类。
package com.zhang.chapter8;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 动态代理
* 核心:运行时创建代理类
*/
public class DynamicProxyTest {
public static void main(String[] args) {
Human human = (Human) ProxyFactory.getProxyInstance(new SuperMan());
//当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法
human.eat("粑粑");
ClothFactory proxyClothFactory = (ClothFactory) ProxyFactory.getProxyInstance(new NikeClothFactory());
proxyClothFactory.produceCloth();
}
}
//接口
interface Human {
void eat(String food);
}
//被代理类
class SuperMan implements Human {
@Override
public void eat(String food) {
System.out.println("超人喜欢吃:" + food);
}
}
//产生代理类对象
class ProxyFactory {
//创建一个代理类对象并返回
public static Object getProxyInstance(Object obj) {
Handler handler = new Handler();
handler.bind(obj);
/*
生成一个代理类的对象并返回。 反射的方法中有 三个参数
1.类加载器,被代理类是由哪个类加载器加载的,代理类要跟他一样,所以传进来
2.class数组,被代理类实现了哪些接口,代理类也要跟他一样
3.InvocationHandler(调用处理器接口):解决的问题是当代理类调用方法的时候,自动去调被代理类中的同名方法
*/
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
}
}
//创建一个类实现 InvocationHandler 接口,自定义一个调用处理器,实现"调用代理类的方法的时候,自动调用被代理类中的同名方法
class Handler implements InvocationHandler {
private Object object;
public void bind(Object object) {
this.object = object;
}
/*
三个参数:
1.代理类的对象(返回的对象)
2.代理类调用的方法,method也是哪个方法(实现当调用代理类中的方法的时候,也能够调用被代理类中的同名方法)
3.方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object value = method.invoke(object, args);
return value;
}
}
用处理器接口):解决的问题是当代理类调用方法的时候,自动去调被代理类中的同名方法
*/
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
}
}
//创建一个类实现 InvocationHandler 接口,自定义一个调用处理器,实现"调用代理类的方法的时候,自动调用被代理类中的同名方法
class Handler implements InvocationHandler {
private Object object;
public void bind(Object object) {
this.object = object;
}
/*
三个参数:
1.代理类的对象(返回的对象)
2.代理类调用的方法,method也是哪个方法(实现当调用代理类中的方法的时候,也能够调用被代理类中的同名方法)
3.方法的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object value = method.invoke(object, args);
return value;
}
}