运算符号
运算符 | 运算 |
---|---|
<< |
左移 |
>> |
右移 |
>>> |
无符号右移[只对无符号有影响] |
& |
与运算 |
` | ` |
^ |
异或运算 |
~ |
反码 |
重载的定义:一个类中,如果出现了两个或者两个以上的同名函数,只要他们的参数的个数活着参数的类型不同,即可称之为该函数重载了。
重写的定义:父类与子类之间的多态性,对父类的函数进行重新定义。如果在子类中定义某方法与其父类有相同的的名称和参数,我们说该方法被重写。
java内存管理。
java运行时数据区域
java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和摧毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。java虚拟机所管理的内存如下:
内存 | aa |
---|---|
方法区 | aa |
虚拟机栈 | bb |
本地方法栈 | cc |
堆 | dd |
程序计算器 | ff |
程序计数器
程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能。
为了现场切换后能恢复到正确的执行位置,每条现场都需要有一个独立的程序计数器,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
java虚拟机栈
与程序计数器一样,java虚拟机栈也是线程私有的,它的生命周期与线程相同。java方法执行的内存模式,每一个方法被执行时会同时创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。
其中64位长度的long和double类型的数据会占用2个局部变量空间,其余的数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定,在方法运行期间不会改变局部变量表的大小。
java程序编译和运行的过程:
编译。创建完源文件之后,程序会先被编译为.class文件。java编译一个类时,如果这个类所依赖的类还没有被编译,编译器就会先编译这个被依赖的类,然后引用,否则直接引用,这个有点像make。如果java编译器在指定目录下找不到该类所其依赖的类.class文件或者.java源文件的话,编译器会报“can’t find symbol”的错误。
编译后的字节码文件格式主要分为两部分:常量池和方法字节码。常量池记录的是代码出现过的所有token(类名、成员变量名等等)以及符号引用(方法引用,成员变量引用等等);方法字节码放的是类中各个方法的字节码。下面显示两张图片:
运行:java类运行的过程大概分为两个过程:1 类的加载 2类的执行。需要说明的是:jvm主要在程序第一次主动使用类的时候,才会去加载该类。也就是说,jvm并不是在一开始就把一个程序就所有的类都加载到内存中,而是到不得不用的时候才把它加载进来,而且只加载一次。
下面是程序进行的详细步骤:
匿名对象使用场景:
类中怎么没有定义主函数?
主函数的解释:保证所有类的独立运行,是程序的进入,被jvm调用。
构造函数:用于对对象进行初始化,是给与之对应的对象进行初始化,它具有针对性,函数中的一种。
一个类中,可以有多个构造函数,因为它们的函数名称相同,所以只能通过参数列表来区分。所以,一个类中如果出现多个构造函数。它们的存在是以重载体现。
构造代码块和构造函数有什么区别?
构造代码块:是给所有的对象进行初始化,也就是说,所有的对象都会调用一个代码块。只要对象一建立。就会调用这个代码块。
构造函数:是给与之对应的对象进行初始化。它具有针对性。
public class Dev{
static{
system.out.println("静态代码块");
}
{
system.out.println("构造代码块");
}
public Dev(){
system.out.println("我是一个无惨构造方法")
}
public Dev(String name){
System.out.println("我是一个带有参数的构造方法")
}
}
1. 执行顺序:静态代码块>main方法>构造代码块>构造方法。其中静态代码块只执行一次。构造代码块在每次创建对象是都会执行。
2. 静态代码块的作用:比如我们在调用c语言的动态库时会把.so文件放在此处。
3. 构造代码块的功能:(可以把不同构造方法中相同的共性的东西写在它里面)
Person p=new Persion()
创建一个对象都在内存中做了什么事情?
封装(面对对象特征之一):是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。
好处:将变化隔离;便于使用;提供重用性;安全性;
用this调用构造函数,必须定义在构造函数的第一行。因为构造函数是用于初始化的,所以初始化动作一定要先执行。否则编译失败
static
特点:
final
根据程序上下文环境,java关键字final有这是无法改变或者终态的含义,它可以修饰非抽象类,非抽象类成员方法和变量。
final类不能被继承,没有子类,final类中的方法默认是final的
final方法不能被子类的方法覆盖,但可以被继承
final成员变量表示常量,只能被赋值一次,赋值后不再改变。
final不能用于修饰构造方法。
java中对继承,java只支持单继承。java虽然不直接支持多继承,但是可实现多接口。
final特点:
抽象类的细节:
抽象类与接口:
抽象类:一般用于描述一个体系单元,将一组共性内容进行抽取,特点:可以在类中定义抽象内容让子类实现,可以定义非抽象内容让子类直接使用。它里面定义的都是一些体系中的基本内容。
接口:一般用于定义对象的扩展功能,是在继承之外还需这个对象具备的一些功能。
抽象类和接口的区别:
多态:函数本身就具备多态性,某一种事物有不同的具体的体现。
体现:弗雷引用或者接口的引用指向了自己的子类对象。父类可以调用子类中覆写过的。
多态的好处:提高了程序的扩展性。继承的父类或者接口一般是类库中的东西,只有通过子类覆写要改变的某一个方法,这样在通过将弗雷的应用指向子类的实例区调用覆写过的方法就行了。
多态的弊端:当父类引用指向子类对象时,虽然提高了扩展性,但是只能访问父类中具备的方法,不可以访问子类中特有的方法。
多态的前提:
具体方法:
内部类:
内部类直接访问外部类成员,用自己的实例对象;
外部类访问内部类要定义内部类的对象;
throw和throws区别:
throws是用来声明一个方法可能排除的所有异常信息,则throw则是指抛出的一个具体的异常类型。此外throws是将异常但是不处理,而是将异常往上传,谁调用我就交给谁处理。
throws用于抛出异常类,后面跟的异常类名,可以跟多个,用逗号隔开。throws 用在函数上。
返回当前线程的名称:Thread.currentThread().getName().
线程要运行必须通过类中的指定的方法开启。start方法。(启动后,就多了一条执行路径)start方法:1启动了线程 2 让jvm调用run方法。
Thread类中run()和start()方法的区别:
start():用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用thread类的start方法启动一个线程,这时此线程处于就绪状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,run方法运行结束,此线程随即终止。
run():run()方法只是类的一个普通方法而已,如果直接调用run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。
总结:start()方法最本质的功能是从cpu中申请另一个线程空间来执行run()方法中的代码,它和当前的线程是两条线,在相对独立的线程空间运行,也就是说,如果你直接调用线程对象的run()方法,当然也会执行,但那时,在当前线程中执行,run()方法执行完成后继续执行下面的代码。而调用start()方法后,run方法的代码会和当前线程并发或并行执行。所以请记住一句话:调用线程对象的run方法不会产生一个新的线程,虽然可以达到相同的执行结果,但执行过程和执行效率不同。
创建线程的第一种方式:继承Thread,由子类复写run方法。
线程状态:
创建线程的第二种方式:实现一个接口Runnable。
为什么要有Runnable接口的出现?
synchronized关键字
synchronized关键字
synchronzied关键字,它包括两种用法:synchronzied方法和synchronzied方法
public synchronized void accessVal(int newVal)
解决安全问题的原理:
只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行就可以解决这个问题。
如果保障共享数据的线程安全呢?
格式:
synchoronzied(对象){//任意对象都可以,这个对象就是共享数据。
需要被同步的代码
}
同步的第二种表现形式:
同步函数:其实就是将同步关键字定义在函数上,让函数具备了同步性。
同步函数是用的哪个锁呢?
通过验证,函数都有自己所属的对象this,所以同步函数所使用的锁就是this锁。
同步代码块和同步函数的区别?
同步代码块使用的锁可以是任意对象。
同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。
懒汉式,延迟加载方式。
class Singel{
private static Single s=null;
private Single(){}
public static Single getInstance(){
if(s==null){
synchronized(Single.class){
if(s==null){
s=new Single();
}
}
}
}
}
等待唤醒机制:
wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。
notify:唤醒线程池中某一个等待线程。
norifyAll:唤醒的是线程池中的所有线程。
注意:
wait和sleep区别:分析这两个方法;从执行权和锁上来分析:
wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。
sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)
**wait:线程会是否执行权,而且线程会释放锁。
sleep:线程会释放执行权,但不是不释放锁。**
线程的停止:通过stop方法就可以停止线程,但是这个方式过时了。
怎么结束run方法?
一般run方法里肯定定义循环。所以只要结束循环即可。
第一种方式:定义循环的结束标记。
第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。
interrupt():中断线程。
setPriority(int newPriority)更改线程的优先级。
getPriority()返回线程的优先级。
toString():放回该坚持的字符串表示形式,包括线程名称、优先级和线程组。
Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。
setDaemon(true):将该线程标记为守护线程或用户线程。将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,java虚拟机退出。该方法必须在启动线程前调用。
join:临时加入一个线程的时候可以使用join方法。
当A线程执行到了B线程的join方式。A线程处于冻结状态,释放了执行权,B开始执行。A什么时候执行呢?只有当B线程运行结束后,A才从冻结状态恢复运行状态执行。
Lock的出现替代了同步:lock.lock();…lock.unlock();
lock接口:多线程在JDK1.5版本升级时,推出一个接口Lock接口。
解决线程安全问题使用同步的形式,其实最终使用的都是锁机制。
到了后期版本,直接将锁封装成了对象。线程进入同步就是具备了锁,执行完,离开同步,就是释放了锁。
集合框架,用于存储数据的容器。
对于集合容器,有很多种。因为每一个容器的自身特点不同,其实原理在与每个容易的内部数据结构不同。
集合容器在不断向上抽取过程中。出现了集合体系。
List | 有序(元素存入集合的顺序和去除的顺序一致),元素都有索引。元素可以重复 |
---|---|
ArrayList | 底层的数据结果是数组,线程不同步,ArrayList替代了Vector,查询元素的速度非常快。 |
LinkedList | 底层的数组结构是链表,线程不同步,增删元素的速度非常快。 |
Vector | 底层的数据结构就是数组,线程同步,Vector无论查询和增删都巨慢。 |
可变长度数组的原理:
当元素超出数组长度,会产生一个新数组,将原数组的数据复制到新数组中,在将新的元素添加到新数组中。
ArrayList:是按照原数组的50%延长。构造一个初始容量为10的空列表。
Vector:是按照原数组的100%延长。
Set | Set接口中的方法和Collection中方法一致的。Set接口取出方式只有一种,迭代器 |
---|---|
HashSet | 底层数据结果是哈希表,线程是不同步的。无序,高效;HashSet集合保证元素唯一性;通过元素的hashCode方法,和equals方法完成的。当元素的hashCode值相同时,才继续判断元素的equal是是否为true。 |
LinkedHashSet | 有序,hashset的子类 |
TreeSet | 对Set集合中的元素的进行指定顺序的排序。不同步。TreeSet底层的数据结构就是二叉树。 |
对于ArrayList集合,判断元素是否存在,或者删元素底层依据都是equals方法。
对于HashSet集合,判断元素是否存在,或者删除元素,底层依据的是hashCoede方法和equals方法。
Map | Map集合 |
---|---|
Hashtable | 底层是哈希表数据结构,是线程同步的。不可以存储null键,null值。 |
HashMap | 底层是哈希表数据结构,是线程不同步的。可以存储null键,null值。替代了hashtable。 |
TreeMap | 底层是二叉树结构,可以对map集合中的键进行制定顺序的排序 |
Map集合存储和Collection有着很大不同:
Collection一次存一个元素;Map一次存一对元素。
Collection是单列集合;Map是双列集合。
Map中的存储的一对元素:一个是键,一个是值,键与值之间有对应关系。
想要获得map中的所有元素:
原理:map中是没有迭代器的,Collection具备迭代器,主要将map集合转成set集合,可以使用迭代器。之所以转成set,是因为map集合具备这键的唯一性,其实set集合就来自于map,set集合底层其实用的就是map的方法。
取出map集合中所有元素的方式一:keySet()方式
Set keySet=map.keySet();
Iterator it=keyset.iterator();
while(it.hasNext()){
Object key=it.next();
Object value=map.get(key);
system.out.printlin(key+":"+value)
}
取出map集合中所有元素的方式二:entrySet()方法。
Set entrySet=map.entrySet();
Iterator it=entrySet.iterator();
while(it.hasNext()){
Map.Entry me=(Map.Entry)it.next();
System.out.println(me.getKey+":"+me.getValue())
}
将非同步集合转成同步集合的方法:Collections中的XXX synchronziedXXX(XXX);
List synchronizedList(list);
Map synchronizedMap(map);
原理:定义一个类,将集合所有的方法加同一把锁后返回。
Collection 和Collections的区别:
Collections是一个java.util下的类,是针对集合类的一个工具类,提供一系列静态方法,实现对集合的查询,排序、体会、线程安全化等操作。
Collection是个java.util下的接口,它是各种集合结构的父接口,继承与它的结构主要是 List,提供了关于集合的一些操作,如插入、删除、判断一个元素是否其成员、遍历等。
自动拆装箱:java中数据类型分为两种:基本数据类型 引用数据类型(对象)
在java程序中所有的数据都需要当做对象来处理,针对8中基本数据类型提供了包装类。
int –> Integer
byte –> Byte
short –> Short
long –> Long
char –> Character
double –> Double
float –> Float
boolean –> Boolean
反射技术:其实就是动态加载一个指定的类,并获取该类中的所有的内容。并将字节码文件中的内容都封装成对象,这样便于操作这些成员。简单来说:反射技术可以对一个类进行解剖。
放射的好处:大大的增强了程序的扩展性。
反射的基本步骤:
获取这个class对象,有三种方式:
反射的用法:
反射类的成员方法:
Class clazz=Person.class;
Method method=clazz.getMethod(methodName,new Class[]{paramClazz1,paramClazz2});
method.invoke();
反射类的构造函数
Constructor con=clazz.getConstructor(new Class[]{paramClazz1,paramClazz2,...});
con.newInstance(params...)
反射类的属性;
File field=clazz.getField(fileName);
file.setAccessible(true);
file.setObject(value);
情况下,被反射的类,内部通常都会提供一个公有的空参数的构造函数。
// 如何生成获取到字节码文件对象的实例对象。
Class clazz = Class.forName("cn.itcast.bean.Person");//类加载
// 直接获得指定的类型
clazz = Person.class;
// 根据对象获得类型
Object obj = new Person("zhangsan", 19);
clazz = obj.getClass();
Object obj = clazz.newInstance();//该实例化对象的方法调用就是指定类中的空参数构造函数,给创建对象进行初始化。当指定类中没有空参数构造函数时,该如何创建该类对象呢?请看method_2();
public static void method_2() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
//既然类中没有空参数的构造函数,那么只有获取指定参数的构造函数,用该函数来进行实例化。
//获取一个带参数的构造器。
Constructor constructor = clazz.getConstructor(String.class,int.class);
//想要对对象进行初始化,使用构造器的方法newInstance();
Object obj = constructor.newInstance("zhagnsan",30);
//获取所有构造器。
Constructor[] constructors = clazz.getConstructors();//只包含公共的
constructors = clazz.getDeclaredConstructors();//包含私有的
for(Constructor con : constructors) {
System.out.println(con);
}
}
反射指定类中的方法:
//获取类中所有的方法。
public static void method_1() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
Method[] methods = clazz.getMethods();//获取的是该类中的公有方法和父类中的公有方法。
methods = clazz.getDeclaredMethods();//获取本类中的方法,包含私有方法。
for(Method method : methods) {
System.out.println(method);
}
}
//获取指定方法;
public static void method_2() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
//获取指定名称的方法。
Method method = clazz.getMethod("show", int.class,String.class);
//想要运行指定方法,当然是方法对象最清楚,为了让方法运行,调用方法对象的invoke方法即可,但是方法运行必须要明确所属的对象和具体的实际参数。
Object obj = clazz.newInstance();
method.invoke(obj, 39,"hehehe");//执行一个方法
}
//想要运行私有方法。
public static void method_3() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
//想要获取私有方法。必须用getDeclearMethod();
Method method = clazz.getDeclaredMethod("method", null);
// 私有方法不能直接访问,因为权限不够。非要访问,可以通过暴力的方式。
method.setAccessible(true);//一般很少用,因为私有就是隐藏起来,所以尽量不要访问。
}
//反射静态方法。
public static void method_4() throws Exception {
Class clazz = Class.forName("cn.itcast.bean.Person");
Method method = clazz.getMethod("function",null);
method.invoke(null,null);
}