将数据和基于数据的 *** 作封装在一起,数据被保护在抽象数据类型的内部,尽可能的隐藏内部的细节,只保留一些对外的接口使之与外部发生联系,其好处在于:
- 可以对成员进行更加自由的修改,比如设置私有属性age的时候,通过函数来setAge(),这样就可以对入参进行判断
- 隐藏实现细节,只对外提供必要的接口,使得程序的逻辑更好
继承是使用已存在的类作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以使用父类的功能,但是不能选择性的继承父类,特点如下:
- 子类可以拥有父类的所有属性和方法,但是父类中的私有属性和方法,子类是无法访问的,只能拥有,但是不能使用
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展
- 子类可以用自己的方法重写父类的方法
构造器:
- 构造器只能被调用,不能被继承,子类实例化对象的时候,如果不显示调用父类构造器,会默认调用父类的无参构造器,如果父类没有无参构造器,会导致编译无法通过
- 如果子类需要使用有参构造器,必须显示的调用super(参数),且super必须是方法的第一行语句
继承的缺点:
继承是一种强耦合关系,父类边,子类就必须变,如果不需要使用向上转型,尽量使用组合与聚合代替继承
多态多态指的是同一种行为具有不同的表现新式,运行同一段代码,运行时会根据调用对象的不同,而产生不同的结果,实现多态有两个必要条件:
- 继承重写:子类继承父类并且重写父类方法
- 向上转型:父类引用指向子类对象
多态的特点是:当父类对象引用变量引用子类对象的时候,被引用对象的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在父类中被定义过的,也就是被子类覆盖的方法;在Java中多态又分为继承多态和接口多态,以下是一个继承多态的实例,借这个实例理解一下该特点:
public class Main {
public static void main(String[] args) {
A a1=new A();
A a2=new B();
B b=new B();
C c=new C();
D d=new D();
System.out.println("a1测试");
a1.test(b);
a1.test(c);
a1.test(d);
System.out.println("a2测试");
a2.test(b);
a2.test(c);
a2.test(d);
System.out.println("b测试");
b.test(b);
b.test(c);
b.test(d);
}
}
class A{
public void test(D d){
System.out.println("这是A中的参数D方法");
}
public void test(A a){
System.out.println("这是A中的参数A方法");
}
}
class B extends A{
public void test(B b){
System.out.println("这是B中的参数B方法");
}
public void test(A a){
System.out.println("这是B中的参数A方法");
}
}
class C extends B{
}
class D extends B{
}
分别对单个对象传入入参bcd,打印结果如下:
a1测试
这是A中的参数A方法
这是A中的参数A方法
这是A中的参数D方法
a2测试
这是B中的参数A方法
这是B中的参数A方法
这是A中的参数D方法
b测试
这是B中的参数B方法
这是B中的参数B方法
这是A中的参数D方法
a1的测试数据: 由于a1是A的对象,所以调用的方法限制在A中,传入bcd的对象,由于bc没有具体的方法,只能向上转型使用test(A a),而d可以使用test(D d)
a2的测试数据:在父类引用指向子类实例的时候,可以由如下的想法,先去父类中看有没有可以使用的方法,再去子类中看该方法有没有被重写,因此这种情况下B类的方法test(B b)实际上是被屏蔽的,这也是为什么a2.test(B b)没有被调用
b的测试数据:此时使用的是b实例,自然是先观察B类中可用的方法,对于参数b,自然调用test(B b),对于参数d,由于我们使用的是继承,继承了A中所有的方法,能找到具体的函数test(D d)那么就调用父类中的该方法,对于参数C,子类和父类都没有相似的方法,将C向上转型一次就可以使用B中的方法test(B b)
根据该例子我们可以总结出一套调用规则:
- 对于可以向上转型的入参,总是会调用参数为最少向上转型次数的方法(包括0次),无论该方法在父类中还是子类中
- 对于子类没有重写的方法,使用父类引用指向子类对象的时候,该方法是被屏蔽的
- 对于重写的方法,使用父类引用指向子类对象的时候,调用父类中的该方法的时候,实际调用的是子类的该方法
当JVM加载一个类的时候,如果该类存在static修饰的成员变量或者方法,就会为他们在固定的位置开辟出一个固定大小的内存区域,同时被static修饰的成员变量和成员方法是被该类所有实例共享的,不依赖于某个特定的实例变量
- 静态内部类:可以不依赖于外部类实例化,只能访问外部类的静态成员变量和静态方法,只加载一次
- 静态方法:不能被重写,不能在内部使用this,super,非静态方法和遍历,在类加载的时候就会被分配和加入内存,可以使用类名.方法名的方法调用
- 静态变量:也叫全局变量,内存中仅有一个,其他性质参考静态方法
- 静态代码块:与静态方法差不多
反射?内部类不可以定义静态成员和方法,但是可以访问外部类的所有内容,通过new外部类构造器.new 内部类构造器来创建对象
在程序运行时,动态加载类并获取类的详细信息,从而 *** 作类或对象的属性和方法,本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息
优点:在运行时获得类的各种内容,进行反编译,能让我们方便的灵活的创建代码,无需在组件之间进行源代码的链接
缺点:反射会消耗一定的系统资源,并且会破坏封装性而导致安全问题
用途:
- 反编译:.class->.java
- 能让我们访问对象的任意属性方法
- 手动加载某个类:比如加载数据库驱动class.forname("com.sql.jdbc.driver")
- 最终要的用途是开发各种通用的框架,比如spring,需要根据配置文件加载不同的类或者是对象,调用不同的方法,这是就必须用到反射在运行时动态加载需要加载的对象
类名.class
对象.getClass()
Class.forName("类的全限定名")
float n=3.4有没有问题?3.4是双精度数,将double赋值给float属于向下转型,可能会造成精度损失,所以必须进行强制类型转换,应该写成3.4f
泛型和泛型擦除是什么?泛型的本质是参数化类型,泛型提供了编译时类型的安全检测机制,该机制允许程序在编译时检测非法类型
在编译阶段采用泛型时加上的类型参数,最终会被编译器去掉,这个过程称为类型擦除,因此泛型主要用于编译阶段,在编译后生成的Java字节码文件中不包含泛型中的类型信息
Queue接口中add/offer,remove/poll,element/peek方法的区别泛型标记的规范:
- E:在集合中使用,表示存放在集合中的元素
- T:表示Java类,包括基本的类以及自定义的类
- K,V:kv键值对中的k,v
- N:表示数值类型
- ?:表示不确定的Java类型
add/offer:都是向队列尾部增加一个元素,区别是当超出队列界限时,add会抛出异常,offer会返回false
remove/poll:当队列为空的时候,remove会抛出异常,poll会返回null
element/peek:当队列为空时,element会抛出异常,peek会返回null
volatile关键字的作用?保证可见性,在线程修改变量的值之后,新的值对于其他线程是可以立即获取的
禁止指令重排,被修饰的变量不会被缓存在寄存器中或者对其他处理器不可见的地方,因此在读取volatile修饰的变量时总会返回最新写入的值
不会执行加锁 *** 作,不会导致线程阻塞
volatile可以保证对单词读写 *** 作的原子性,但是不能保证像i++这种 *** 作的原子性,本质上i++这个 *** 作时读写两次 *** 作
Synchronized的内部包括哪些区域?ContentionList:锁竞争队列,所有请求锁的线程都被放在竞争队列中
EntryList:竞争候选列表,在锁竞争队列中有资格称为候选者来竞争锁资源的线程被移动到候选列表
WaitSet:等待集合,调用wait方法后阻塞的线程被放在WaitSet
OnDeck:竞争候选者,在同一时刻最多只有一个线程在竞争锁资源,此线程的状态称为OnDeck
Owner:竞争到锁资源的线程状态
!Owner:释放锁后线程的状态
简述Synchronized的实现原理- 收到新的锁请求的时候先进行自选,如果自选也没获取到锁的资源,被放入ContentionList(该做法对已经放入队列的线程是不公平的体现了其不公性)
- 为了防止ContentionList尾部的元素被大量线程进行CAS访问影响性能,Owner线程会在释放锁时将ContentionList的部分线程移动到EntryList并且指定某个线程为OnDeck线程,Owner线程并没有将锁直接给他而是把锁竞争的权利交给了它,该行为叫竞争切换,牺牲了公平性但是提高了性能
- 获取到锁的OnDeck线程会变为Owner线程,未获取到的仍停留在EntryList中
- Owner线程在被wait阻塞后会进入WaitSet,知道某个时刻被唤醒再次进入EntryList
- ContentionList,EnrtyList,WaitSet中的线程都处于阻塞状态
- 当Owner线程执行完毕后会释放锁资源并且变为!Owner状态
ReentrantLock?jdk1.6中引入了适应自选,锁消除,锁粗话,轻量级锁以及偏向锁等提高锁的效率,锁可以以偏向锁->轻量级锁->重量级锁方式升级,这种过程也称为锁膨胀
是Lock接口的实现类,是一个可重入的独占锁,通过AQS实现:
- 支持公平,非公平锁
- 提供可响应中断锁(通过interrupt方法中断取消对锁的请求)
- 可轮询锁:通过tryLock获取锁,有可用的锁就立刻返回true否则返回false
- 定时锁:带有long参数的tryLock方法,在给定时间获取到锁且当前线程未被中断返回true,超过时间返回false,如果获取锁时被中断则抛出异常并且清除已终止状态
- 通过lock和unlock显示的加锁释放锁
- 相比于synchronized它采用的是乐观并发而不是悲观并发,它会先尝试使用cas的方式获取锁
lock/unlock:加锁和解锁
newCondition:创建条件对象,使用条件对象管理哪些已经获取了锁但是不满足有效条件的线程,调用await方法把线程进入等待集,调用sign/signall解除阻塞
lockInterruptibly:如果当前线程未被中断则获取该锁
自旋锁?自旋锁任务如果持有锁的线程能在很短的时间内释放锁的资源,那么等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,只需等待小段时间,避免了切换线程的时间消耗
- 其优点在于减少了cpu上下文切换,对于占用锁时间非常短或锁竞争不激烈的代码块来说性能好
- 缺点在于在持有锁线程长时间占有锁或竞争过于激烈的时候,线程会长时间自选浪费cpu资源
- 重量级锁:基于 *** 作系统互斥量实现,会导致进程在用户态和内核态之间的切换,开销大
- 轻量级锁:在没有多线程竞争的前提下,减少重量级锁的使用,适用于线程交替执行同步代码块的情况,如果同一时刻多线程访问同一个锁,会导致轻量级锁膨胀为重量级锁
- 偏向锁:用于某个线程获取锁之后,消除这个线程锁重入的开销,看起来似乎是线程获得了锁的偏袒,偏向锁的目标是在同一个线程多次获取某个锁的情况下尽量减少轻量级锁的执行路径,因为轻量级锁需要多次cas,而偏向锁只需要在切换thread的时候的一次cas *** 作,当出现多线程竞争锁的时候,会撤销偏向锁向轻量级锁膨胀
- 减少锁的持有时间:减少同步代码块对锁的持有时间
- 减小锁的粒度:segment细化
- 读分离:读写锁
- 锁消除
- 锁粗化:锁分的太细反而影响性能,此时可以将关联性强的锁集中处理
是一种标记,可以是类或者接口附加额外的信息,是帮助编译器和JVM完成一些特定的功能的
元注解是自定义注解使用的注解包括@Target:约束注解的位置,@Rentention:用来约束注解的生命周期@Document:表面这个注解应该被javadoc工具记录@Inherited:表明某个注解类型是被继承的
File对象是什么,常用方法?File对象表示的是 *** 作系统上的文件或者目录
- getAbsolutePath():获取绝对路径
- getPath:获取文件定义时使用的路径
- getName:获取文件名
- lengh:返回文件长度,单位是字节
- exists:判断file对象表示的文件或目录是否存在
- isDirctory:判断是否为目录
- isFile:判断是否为文件
- createNewFile:不存在的时候创建新文件
- delete:删除文件
- mkdir:创建一级目录
- mkdirs:创建多级目录
- list:获取当前目录下所有一级文件名,到一个字符串数组返回
- listFiles:获取当前目录下所有一级File对象到File数组返回
抛出异常:遇到异常不进行具体的处理,而是将异常抛给调用者,throws作用在方法上,throw作用在方法内
try/catch/finally捕获异常:jdk1.7开始可以在try后的小括号定义需要关闭的资源,如果资源实现了autoCloseable接口就可以实现自动关闭
如果想在循环里删除元素该怎么做?使用迭代器,调用集合类的iterator.remove,可以在循环中对元素进行删除
使用集合类的removeIf方法,传入一个bool表达式
使用fori循环进行删除的时候需要重置i的位置,即--i;
不能使用foreach遍历的时候进行修改或者删除 *** 作
多线程不可见问题的原因和解决方式不可见的原因是,每个线程都有自己的工作内存,线程都是从主内存拷贝共享变量的副本值,每个线程都是在自己工作内存 *** 作共享变量的
加锁:获得锁之后的线程会将工作内存清空,从主内存拷贝共享变量最新的值称为副本,修改后刷新回主内存,再释放锁
volatile:该关键字修饰的变量会通知其他线程之前读取的值已经失效,线程会加载最新的值到自己的工作内存
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)