JAVA的编译过程
.java (用javac.exe编译) -> .class(字节码文件,不是二进制文件,否则计算机可以直接运行) -> 类装载器 -> java虚拟机(JVM) -> 操作系统
.class文件生成后,.java源文件被删除不影响运行
javac.exe是编译程序,java.exe是运行程序
/**
*
*
*/ 这种注释在编译时会被放入帮助说明书内
一个.java源文件只能有一个public的类,并且必须与源文件同名
每一个class中都可以有一个main方法
JAVA标识符
常量名:全大写
变量名,方法名:首字母小写,后面每个单词首字母大写
类名,接口名:首字母大写,后面每个单词首字母大写
java中局部变量声明后必须赋值才能访问,否则编译报错;而成员变量系统则会自己赋值
成员变量没有手动赋值时,系统自动赋值为:
byte,short,int,long | 0 |
float,double | 0.0 |
boolean | false |
char | \u0000 |
引用数据类型 | null |
java访问变量符合就近原则
public static class
{
public static void main(String[] args)
{
System.out.println(i);
}
static int i =100;
}
这样也可以打印出100
基本数据类型中和C语言不一样的地方
布尔类型为 boolean ,只有 true 或 false ,不能用0和1表示
整数型多了一个 byte 占一个字节的整数位
char 占两个字节
Unicode编码
一种全球通用的的文字
分为:utf-8(常用),utf-16,utf-32
Java采用的是Unicode编码,因此标识符可以用中文
JDK中自带的native2ascii可以把中文转移为unicode编码
long sum = 1111111111222;
如果这样编译会报错,因为1111111111222会被系统当做一个整型来处理,而整型缺省为int型,显然该数字大于int型最大值,因此会报错
解决方法是在数字末尾加上L,告诉编译器是一个long类型的值
long sum = 1111111111222L;
如果long类型要赋值到int类型,会出现精度丢失,因此要使用强制类型转换,规则与C一样
强制类型实现原理
long a = 2147483648L ;
int b = (int)a ;
最后b会等于 -2147483648
因为2147483648 B=00000000 00000000 00000000 00000000 10000000 00000000 00000000 00000000
int只能有4个字节,因此: 10000000 00000000 00000000 00000000 = -214743648
java中所有浮点型默认被当做double类型来处理,如果要用float型,则在末尾添加F
java输入数据
java.util.Scanner str = new java.util.Scanner(System.in);
String strname = str.next();
int intname = str.nextInt();
for(初始化表达式;布尔表达式;更新表达式)
while(布尔表达式)
注意:一定是布尔表达式,不像C语言一样,可以是其他值
continue不一样的用法
continue 循环名称
myFor: for(int i=0;i<10;i++){
if(i==5){
continue myFor;
}
System.out.print(i+"\t");
}
不是void返回值类型的函数必须以return结尾,并且return后不能有任何多余的语句
void 里面可以有return语句,但是不可以有返回值,可以用return语句来结束函数
JVM中内存分为这样三块常用的内存(还有其他的)
1、方法区内存( 静态变量和代码 )
2、堆内存( 实例化对象时存储,字符串 (因为字符串本质是自定义类) )
3、栈内存( 方法调研时压栈 (方法的局部变量) )
封装
有static关键字的方法调用:类名.方法名(),意味着要同构类来调用
无static关键字的方法调用:引用.方法名()
访问控制权限修饰符
public | 在任意位置都可以访问 |
protected | 同包下可以访问,不同包下需要继承才能访问 |
缺省 | 同包下可以访问 |
private | 只有在本类中可以访问 |
类只能用public或缺省来修饰(内部类除外)
构造函数
对于构造方法来说,不需要返回值类型,甚至 void 也不能有
构造函数名要与类名保持一致
构造方法调用方式:new 构造方法名(实参列表);
构造方法也可以重载(但是构造函数是不能被覆盖的)
this指针里存储类自己的地址,所以this指针指向自身
this也可以用于函数的重载上,例如:
public Date(String year)
{
this.year = year;
}
public Date() //当调用这个函数时将年份改为默认的1970年
{
this(1970);
}
如果这个特征是类的属性,而不是每个实例的属性,则用static
例如,中国人这个类中,国籍大家都想同,因此国籍应该是静态变量
方法也是类似
static的方法或属性在类加载时就执行,并且只能执行一次
静态代码块:在类加载时刻执行,并且也只有一次(用于日志的记录)
继承
[修饰符列表] class 类名 extends 父类{}
私有的不继承,构造方法不继承,其他数据都可以被继承
所有类都默认继承Object类
重写时,权限只能更高不能更低,抛出异常只能越多不能越少
如果父类的方法是静态的,可以重写,但是重写时还得是静态的
多态
分为向上转型(自动转换)和向下转型(需要强制类型转换符)
向上转型:Animal a = new Cat();
对于JAVA程序,分为编译阶段和运行阶段
public class human
{
public void run()
{
System.out.println("running");
}
}
public class runningplayer extends human
{
public void run()
{
System.out.println("跑步!");
}
public void fight()
{
System.out.println("fighting!");
}
}
public class Message
{
public static void main(String[] args)
{
human i = new runningplayer();
i.run();
}
}
在编译阶段因为i的引用类型是human类型,而human类型中有run方法,因此编译通过,这个过程我们称为静态绑定
在运行阶段,因为实际创建的对象时runningplayer类型,因此最后 i.run() (不论有没有重写)的结果是 跑步!,这个过程叫做动态绑定,或者运行阶段绑定
向下转型: runningplayer c = (runnningplayer) i; 只有当i不得不使用runningplayer才有的方法fight时,我们才会向下转型
instanceof函数
用于判断某个实例是否属于某个类,是则返回 true 不是则返回 false
例如:a3 instanceof Cat //a3 是否是猫
final关键字
被final修饰的类无法被继承
被final修饰的方法无法被覆盖
被final修饰的变量一旦赋值后,不可重新赋值
被final修饰的实例变量也不可重新赋值
被final修饰的引用,不能再指向其他对象(但其中指向的内存的内容是可以更改的)
当static final一起联用我们就叫做常量,注意常量名全大写
package 和 import
定义package:在java源文件第一行上编写
package只能编写一个语句:package 包名; (包名:公司域名倒序.项目名.模块名.功能名)
包名也是标识符,全部小写,语句结束有分号
包用于存放多个class文件,手动创建文件夹,一个包就是一个文件夹
包的路径就是:公司域名倒序\项目名\模块名\
编译时 javac java源文件路径
运行时 java 包的路径
或者 javac -d . *.java 编译当前目录下所有java文件,会自动生成包路径
-d 后加编译之后存放路径
import 包名 (要写在package语句后,class语句前)
java.lang.* 不需要手动引入,系统自动引入
super关键字
super只能放在构造方法的第一行,当没有写的时候,系统默认有一个super()无参数的构造方法
继承中的构造方法,一定(100%)要先创建了父类,才能创建子类,而super()就是用来创建父类用的
当有this()函数时,可能会执行,this()的构造方法,但最后super()函数一定会运行一次
super()相对于当前对象,一定是在 "栈顶部"
super()和this()一样不能出现在static方法内
super.属性(当父类和子类有相同的属性名字,在子中访问父类的属性时使用)
抽象类
抽象类无法实例化,无法创建对象(因为抽象类的对象对应的是类,而类本身不存在,所以抽象类无法创建对象)
定义:权限访问修饰符 abstract class 类名 {}
抽象类是用来被子类继承的,也可以被抽象类继承
final 和 abstract 不能联合使用
abstract 方法(); 特点:没有方法体
抽象方法只能出现在抽象类中,抽象类不一定要有抽象方法
子类继承抽象类,必须实现对抽象类的所有抽象方法进行覆盖(实现)
接口
权限访问修饰符 interface 接口名{};
接口支持多继承,可以继承多个接口
接口里只包含常量和抽象方法
因此,接口中的方法可以省略public abstract,常量可以省略public static final
类和类之间叫做继承(extends),类和接口之间叫做实现(implements)
当即有继承又有接口时,extend在前,implements在后
抽象类有构造方法,接口没有构造方法
接口比抽象类更常用,接口一般都是对“行为”的抽象(功能)
深克隆和浅克隆
浅克隆
当有一个order1,其中包含一个name属性和item对象
order2 = order1.clone();
如果改变order2.name是不会改变order1.name的,但是如果改变order2.item,则会影响order1.item
因为浅克隆,不会克隆对象 引用的 对象
而深克隆可以
内部类
分为:静态内部类,实例内部类,局部内部类
匿名内部类是因为这个类没有名字
m.sun(new Compute(){
System.out.println("实现内部类");
},200,100
)
用匿名内部类,在方法中直接声明并且实现接口
数组
静态初始化
引用数据类型[] 数组名 = {100,200,300,400};
动态初始化
引用数据类型[] 数组名 = new int[10]; //默认值为0
native 关键字表示函数底层使用的是C++实现方法
数组拷贝是将对象的地址重新拷贝一份,指向的还是同一个地址,而不是重新创建一个对象
枚举类型
枚举类型一般是用来作为方法返回值
枚举也是一种数据类型,编译后也是生成class文件
public enum R{Success,False,Continue}
低版本的JDK,switch只支持Int和char
高版本的JDK,switch支持String和枚举
UML是一种统一建模语言,图标式语言(画UML图的都是软件设计人员,软件构架师,系统分析师)
异常
异常在JAVA中以类的形式存在,每一种异常都可以创建对象
Error 一旦出现,JAVA程序是一定要退出的;Exception 就可以处理
Exception 的子类 ExceptionSubClass(编译时异常(也是在运行阶段)), RuntimeException(运行时异常)
异常处理方式
1. 在方法声明上使用 throws 关键字,抛给上一级
2. 使用 try 和 catch 语句进行异常捕捉
- 方法名() throws 异常名称{}; //一般main方法上不适应throw,因为这样抛给JVM就直接终止程序了,异常处理没有意义
- try{
运行代码
}
catch(异常名称 异常标识符){
异常处理
}
finally{}
方法中异常抛出后的代码是不会被执行的,但是catch后的代码会继续执行,这样就保证了代码的健壮性(保证了服务器不会宕机)
异常信息不是在程序内一起输出的,而是由某个线程异步输出
try{}catch{}语句中catch可以有多个,而且必须从小到大,子类在前,父类在后
JAVA8之后catch()括号里可以写 || 运算符
finally子句中的语句是一定会执行的,不管try和catch有没有执行
如果try语句中有return语句,会先运行finally子句再运行return,return语句一定是最后执行的
如果try语句中有System.exit(0)语句,finally子句就不会运行了
Java中自定义异常
1.编写一个类继承 Exception 或 RuntimeException.
2.提供两个构造方法一个无参一个有参String(有参的构造方法只要用super的就行了)
重写父类方法,子类只能比父类抛出更少的异常;假如父类不抛出异常,子类可以抛出RuntimException
集合
集合不能直接存储基本数据类型,也不能直接存储对象;只能存储引用或者说地址
集合分为两大类:一类是以 单个 的方式存储元素(Collection),另一类是以 键值对 的方式存储元素(Map)
类型 | 底层数据结构 | 安全性 | 说明 |
---|---|---|---|
ArrayList | 数组 | 非线程安全 | |
LinkedList | 双向链表 | 非线程安全 | |
Vector | 数组 | 线程安全 | 效率较低 |
Hash | 哈希表 | 底层new了一个HashMap集合,HashMap集合底层是用哈希表实习的 | |
TreeSet | 二叉树 | new了一个TreeMap集合,TreeMap底层是用二叉树实现的 (存储的数据可以自动按照大小排序) | |
Map | 以键值对的方式存储元素,所有的Map集合都是无序不可重复的 (无序是指没有下标) | ||
HashMap | 哈希表 | 非线程安全 | |
Hashtable | 哈希表 | 线程安全 | 现在用的较少 |
Properties | 线程安全 | key和value只支持String类型 |
Map集合不能使用Iterator,在Collection下的子类才可以用
泛型
没有使用“泛型”之前,Collection中可以存储Object的所有子类型,使用之后就只能存某个具体的类型
用于指定集合中存储的数据类型
List<数据类型> myList = new ArrayList<数据类型>();
JDK高本版之后,后面的new的泛型的数据类型可以不写,JDK拥有自动类型推动,这样的表达式称为钻石表达式
泛型可以自定义
foreach语句
专门用于数组的遍历
for(int data : array){
System.out.println(data);
}
java.io类
以Stream结尾的都是字节流,以er结尾的都是字符流
转换流:(将字节流转换为字符流)
java.io.InputStreamReader
java.io.OutputStreamWriter
Project的根目录就是IDLE的默认当前路径
序列化(Serialize)就是把Java对象存储到硬盘文件中
反序列化(DeSerialize)把硬盘中的数据恢复到内存上形成Java对象
序列化的类要实现Serializable接口
Java中有两种接口,一种是普通接口,另一种是标志接口
而Serializable就是标志接口,用于给JVM识别,JVM会自动生成序列化版本号
transient(游离的)关键字
用于表示改属性或者方法不参与序列化
JVM区分类靠的就是 类名 和 序列化版本号
序列化版本号的缺陷:生成了版本号后不能修改该类的代码,若修改了,之前的版本号的数据就废了,因此我们可以手动生成一个序列化版本号(尽量做到独特)
private static final long serialVersionUID = ........;
经常要改变的信息写在文件里,这样程序再去读取文件,这样程序就不需要反复编译,服务器也不需要重启,这样的文件叫做 配置文件
并且内容格式为:key = value 时,我们叫做属性配置文件
java中属性配置文件建议以 .properties 结尾,在配置文件中#是注释
如果key重复了,value会自动覆盖
多线程
多线程三大特性
- 原子性,可见性,有序性
进程间内存不共享
线程在Java中,方法区和堆内存共享,栈内存独立(一个线程一个栈)
实现多线程有两种方法:
方法一:
编写一个类,继承java.lang.Thread类,重写run方法(run()方法就是类似主线程中的main()方法)
在主线程中创建线程对象,然后再用 对象.start() 就可以启动该线程了
start()方法的作用是在JVM中开辟一个新的栈空间,开辟完这句代码瞬间就结束了
线程启动后会自动调用run()方法,而且run()在栈底
方法二:
编写一个类,实现java.lang.Runnable接口,再将该对象封装成一个线程类
Thread t = new Thread(new MyRunnable());
第二种比第一种更加常用,因为类实现了接口还可以去继承其他类
当线程没有设置名字时,线程默认名字为Thread-0,Thread-1,Thread-2
Thread的run()方法没有实现Throwable,因此我们写的run()方法不能抛出比父类更多异常,只能try和catch
t.stop() 可以终止一个进程,但是已过时了,因为会造成资源丢失
现在都用t.run = false这种信号量的方法
java中的优先级
默认为5,最低为1,最高为10
synchronized(){
线程同步代码块;
}
括号内填入共享资源,可以是对象
同步代码块越小,效率越高
synchronized如果写在实例方法上,锁的一定是this,不灵活,而且同步范围扩大了,效率降低(保证实例变量安全)
如果在静态方法上使用synchronized,那么就是类锁,类所只有一把(保证静态变量安全)
synchronized在开发中最好不用嵌套使用,容易死锁
线程分为两种:1、用户线程 2、守护线程
守护线程一般是一个死循环,只要用户线程结束,一般守护线程会自动结束
实现线程的第三种方法!(JDK8之后)实现Callable接口,可以获取一个线程的执行结果
要引入java.util.concurrent.FutureTask包
创建一个“未来任务类”对象
FutureTask task = new FutureTask(new Callable(){});
//括号内要传入一个Callable接口实现对象,也可以是匿名内部类
Thread t = new Thread(task); //创建线程对象
task.get(); //获取t线程的执行结果
定时器
java.util.Timer类可以直接拿来用
但在实际开发中用最多的是Spring框架中的SpringTask框架
wait和notify方法(Object类自带的)
Object o = new Object();
o.wait(); //让对象o进行无限等待,直到被notify唤醒为止
变长参数
public static void m(int... args){} //可变长度参数
可变长度参数只能出现一个,而且在参数列表最后面,可变长度参数可以看成一个数组
反射机制
反射机制可以操作字节码文件
java.lang.reflect
java.lang.Class 代表字节码文件
java.lang.reflect.Method 代表方法字节码
java.lang.reflect.Constructor 代表构造器字节码
java.lang.reflect.Field 代表属性字节码
String s = "abc";
Class x = s.getClass();
Class c = Class.forName("java.lang.String"); //forName的执行会导致类加载(类加载时,静态代码块执行)
System.out.println(x==c); //返回true
Class y = String.class;
System.out.println(y==c); //返回true
通过反射机制,可以在不改变java源代码的基础之上,做到不同类型的对象实例化
原始路径方式对于代码来说可移植性差
String path =
Thread.currentThread().getContextClassLoader().getResource("FileName").getpath();
//获取当前线程,获取当前线程下的类加载器,从类的跟目录开始加载资源FileName,获取它的路径
InputStream in =
Thread.currentThread().getContextClassLoader().getResourceAsStream("FileName");
//以流的方式返回
FileName为src下的相对路径
资源绑定器(properties文件必须在类路径下)
import java.util.ResourceBundle;
ResourceBundle bundle = ResourceBundle.getBundle("FileName");
String className = bundle.getString("key");
类加载器(启动类加载器,扩展类加载器,应用类加载器)
(父加载器)启动类加载器会加载:jre\lib\rt.jar (java中最核心的类库)
(母加载器)扩展类加载器会加载:jre\lib\ext\*.jar
应用类加载器会加载:classpath下
java中为了保证类加载的安全,是用来双亲委派机制,优先重双亲加载器中加载(先父后母)
反射Field
Field[] fields = studentClass.getFields(); //获取的是公开的属性
Field[] fields = studentClass.getDeclaredFields(); //获取所有属性
Field[0].getName(); //获取属性名
Field[0].getType(); //获取属性类型
Field[0].getType().getSimpleName(); //获取简洁的属性类型
int i = Field[0].getModifiers(); //获取修饰符数字(数字是修饰符枚举类型代号)
Modifier.toString(i); //将数字类型转换为修饰符字符串
Field no = studentClass.getDeclaredField("FieldName"); //获取名为FieldName属性
no.set(对象,value); //给对象的no属性赋值value
no.get(对象); //获取对象的no属性值value
反射机制缺点:no.setAccessible(true);会打破封装,可能对安全性造成影响
反射Method
Class u = Class.forName("ClassName");
Object obj = u.newInstance(); //实例化一个对象
Method[] methods = u.getDeclaredMethods(); //获取所有方法
methods.getReturnType(); //获取返回类型
Class[] p = methods[i].getParameterTypes(); //获取参数列表
Method fun = u.getDeclaredMethod("funName",int.class,String.class);
//括号里填方法名和参数列表里的参数类型
fun.invoke(obj,1,"abc"); //调用obj对象的fun方法,将参数1和"abc"传入
反射Constructor
Class v = Class.forName("ClassName");
Constructor constructors = v.getDeclaredConstructors(); //获取所有构造方法
constructors[i].getModifiers(); //获取构造
Constructor con = c.getDeclaredConstructor(int.class,String.class); //获取指定的构造方法
con.newInstance(110,"jackson"); //实例化对象
反射父类和接口
Class superclass = v.getSuperclass(); //获取父类
Class[] interfaces = v.getInterfaces(); //获取接口列表
注解
注解编译后也是xxx.class文件,是一种引用数据类型
理论上注解可以出现在任何地方
格式:[修饰符列表] @interface 注解类型名{}
@Deprecated
//用@Deprecated注释的程序元素,不鼓励程序员使用,通常因为有更好的选择或者它很危险//表示该元素已过时
@Override
//表示重写父类一个方法
@SuppressWarnings
//指示应该在注释元素中取消指定的编译器警报
元注解是注解里的注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
@Target
//用来标注“被标注的注解”可以出现在哪些位置上
@Retention
//用来标注“被标注的注解”最后保存在哪里
注解中的属性
String name() default "abc"; //看着像是一个方法,但其实是一个属性,默认值为abc
在注解中有属性的,在写注解时必须给属性赋值(除非有默认值)
当注解中只有一个属性名并且是value时,在给属性赋值时value属性名可以省略
注解中的属性可以是数组类型
@Retention(RetentionPolicy.RUNTIME) //这样的“被注解元素”可以被反射
c.isAnnotationPresent(MyAnnotation.class); //判断是否存在MyAnnotation注解
MyAnnotation ca = c.getAnnotation(MyAnnotation.class); //获取注解对象
ca.属性名(); //获取注解中的属性值
类中的属性默认是私有的