Java - 基础知识整理
编译型语言与解释型语言
计算机是不能理解高级语言的,更不能直接执行高级语言,所以任何的高级语言编写的程序,要想被计算机执行,都必须将其转换成计算机可以识别的机器语言,也就是机器码。
这种转换方式有两种:
- 编译
- 解释
据此,高级语言可以被划分为解释性语言和编译型语言。
主要区别在于:
编译型语言:源程序编译之后即可在该平台运行,所以运行速度相对较快。
解释性语言:在运行期间编译,所以其跨平台性较好。
编译型语言
使用专门的编译器,针对特定的平台,将高级语言的源代码一次性的编译成平台硬件可以执行的机器码,并包装成该平台所能识别的可执行性程序的格式。
特点:
在程序执行之前需要一个专门的编译过程,将源代码编译成硬件平台可以识别的机器码,譬如exe格式的文件,之后再运行时,直接使用编译结果即可,如直接运行exe文件。因此,只需要编译一次,所以其执行效率高。
- 一次性的编译成平台相关的机器语言文件,运行是脱离开发环境,运行效率高
- 与特定平台有关,一般无法移植到其他平台。(C,C++,Objective等)
解释型语言
使用专门的解释器对源代码逐行解释成特定平台的机器码,并立即执行。注意:是代码在执行是才被解释器逐行动态翻译和执行,而不是在执行之前就完成翻译的。
特点:
解释型语言不需要提前编译,是直接动态的将源代码解释成机器码并立即执行,因此只有某一平太提供了相应的解释器即可运行该程序。
- 解释型语言每次运行都需要将源代码解释成机器码并执行,所以效率低下。
- 只要某一平台提供了相应的解释器,就可以运行该程序,方便移植。(Python等)
JAVA是解释型语言还是编译型语言?
个人认为:java兼具两种语言的特性:既需要一次编译,又需要一个特定的运行环境(JVM),在任何一个平台上,只有运行的有JVM,即可运行Java编译后的字节码(.class)文件
按值传递与按引用传递
按值传递
按值传递类似与把值复制一份过去,不会对原有的变量值产生影响。
基本数据类型都是按值传递的。
按引用传递
按引用传递其实就是按引用值传递,传递的是变量的引用地址,给参数传递的是同一个引用地址,传递后参数和变量指向的是同一个引用地址,因此在方法中改变了变量内容,将会影响到变量本身。
引用类型的变量传递都是按引用传递的。
示例:
package com.example.test;
public class DemoJavaSe {
public static void main(String[] args) {
//测试按值传递和按引用传递
testDiliverWay();
}
/**
* 测试按值传递和按引用传递
*/
public static void testDiliverWay(){
int a = 1;
long b = 2l;
int[] intArr = new int[]{1,2,3};
System.out.println("old value for a:"+a);
System.out.println("old value for b:"+b);
System.out.println("old value for intArr:"+intArr[1]);
testDili(a,b,intArr);
System.out.println("new value for a:"+a);
System.out.println("new value for b:"+b);
System.out.println("new value for intArr:"+intArr[1]);
}
private static void testDili(int aa, long bb, int[] intArr){
aa = aa+10;
bb = bb+10l;
intArr[1] = 10;
}
}
/*
输出结果:
old value for a:1
old value for b:2
old value for intArr:2
new value for a:1
new value for b:2
new value for intArr:10
*/
java编译命令
待续。。。
Java数据类型
Java中数据类型主要分为两类:
- 基本数据类型
- 引用数据类型
Java基本数据类型
Java引用数据类型
- 引用类型执行一个对象,执行对象的变量成为应用变量。
- 对象、数组都是引用数据类型。
- 所有引用类型的默认值都是null
- 一个引用变量可以用来引用任何与之兼容的类型。
Java修饰符
Java语言提供了很多修饰符,修饰符主要用来定义类、方法、变量等,通常放在语句的最前端。
Java的修饰符主要分为两个大类:
- 访问控制修饰符
- 非访问修饰符
访问控制修饰符
修饰符 | 当前类 | 同一个包内 | 子孙类(同一个包) | 子孙类(不同包) | 其他包 |
---|---|---|---|---|---|
public | Y | Y | Y | Y | Y |
protected | Y | Y | Y | Y/N | N |
default | Y | Y | Y | N | N |
private | Y | N | N | N | N |
protected的说明: |
- 子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问;
- 子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的protected方法。
非访问控制修饰符
static
final
abstract
synchronized
transient
volatile
继承和多态
抽象类
接口
数组
用来存储同一类型数据的容器。
数组的声明方式
//方式一
int[] ints = new int[4];//初始话长度为4,每个默认值都是0
//方式二
int[] ints1 = new int[]{1,2};//长度为2,默认给了初始值
//方式三
int[] ints2 = {1,2};//初始化长度2,默认给了初始化值
//定义二维数组
int[][] ints3 = {{1,2,3},{3,4},{5,6}};
/*
* [1,2,3
* 3,4
* 5,6]
* */
数组的特点:
- 长度是确定的,数组一旦声明,其长度就确定了。
- 同一个数组中的元素必须相同。
- 获取数组中的元素只能通过脚标获取。
java集合
存放某一类数据的集合。不同的数据结构用来实现不同的集合,数组结构用于List集合,hash结构可以用于Map集合,链表结构用于LinkedList,LinkedMap
Collection 接口的接口 对象的集合(单列集合)
├——-List 接口:元素按进入先后有序保存,可重复
│—————-├ LinkedList 接口实现类, 链表, 插入删除, 没有同步, 线程不安全
│—————-├ ArrayList 接口实现类, 数组, 随机访问, 没有同步, 线程不安全
│—————-└ Vector 接口实现类 数组, 同步, 线程安全
│ ———————-└ Stack 是Vector类的实现类
└——-Set 接口: 仅接收一次,不可重复,并做内部排序
├—————-└HashSet 使用hash表(数组)存储元素
│————————└ LinkedHashSet 链表维护元素的插入次序
└ —————-TreeSet 底层实现为二叉树,元素排好序
Map 接口 键值对的集合 (双列集合)
├———Hashtable 接口实现类, 同步, 线程安全
├———HashMap 接口实现类 ,没有同步, 线程不安全-
│—————–├ LinkedHashMap 双向链表和哈希表实现
│—————–└ WeakHashMap
├ ——–TreeMap 红黑树对所有的key进行排序
└———IdentifyHashMap
这里涉及到数据结构。
线程
线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程包含以下内容:
- 一个指向当前被执行指令的指令指针;
- 一个栈;
- 一个寄存器值的集合,定义了一部分描述正在执行线程的处理器状态的值
- 一个私有的数据区。
在Java中,每次程序运行至少启动2个线程:一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM实际上就是在操作系统中启动了一个进程。
Java线程的声明方式
1.继承Thread类,实现线程
run()为线程类的核心方法,相当于主线程的main方法,是每个线程的入口,是启动线程后程序执行的入口
- 一个线程调用 两次start()方法将会抛出线程状态异常,也就是的start()只可以被调用一次
- native生明的方法只有方法名,没有方法体。是本地方法,不是抽象方法,而是调用c语言方法registerNative()方法包含了所有与线程相关的操作系统方法
- run()方法是由jvm创建完本地操作系统级线程后回调的方法,不可以手动调用(否则就是普通方法)
package com.example.test.thread;
public class CustomThreadByExtendsThread extends Thread{
public void run() {
for(int i=0;i<5;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public static void main(String[] args) {
CustomThreadByExtendsThread mThread1=new CustomThreadByExtendsThread();
CustomThreadByExtendsThread mThread2=new CustomThreadByExtendsThread();
CustomThreadByExtendsThread myThread3=new CustomThreadByExtendsThread();
mThread1.start();
mThread2.start();
myThread3.start();
}
}
/*结果:
Thread-1:0
Thread-1:1
Thread-1:2
Thread-1:3
Thread-1:4
Thread-2:0
Thread-2:1
Thread-0:0
Thread-2:2
Thread-2:3
Thread-2:4
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
*/
Thread
类也是实现了Runnable
接口
而Runnable
接口中只定义了一个抽象方法
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface {@code Runnable} is used
* to create a thread, starting the thread causes the object's
* {@code run} method to be called in that separately executing
* thread.
* <p>
* The general contract of the method {@code run} is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
2.实现Runnable
接口,覆写run()
方法(推荐)
因为实现Runnable
接口,可以避免Java单继承的局限性,因此推荐这种方式。
package com.example.test.thread;
public class CustomeThreadByImplRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<5;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public static void main(String[] args) {
CustomeThreadByImplRunnable customeThreadByImplRunnable=new CustomeThreadByImplRunnable();
Thread mThread1=new Thread(customeThreadByImplRunnable,"线程1");
Thread mThread2=new Thread(customeThreadByImplRunnable,"线程2");
Thread mThread3=new Thread(customeThreadByImplRunnable,"线程3");
mThread1.start();
mThread2.start();
mThread3.start();
}
}
/*
线程1:0
线程3:0
线程2:0
线程2:1
线程2:2
线程3:1
线程1:1
线程3:2
线程2:3
线程1:2
线程2:4
线程3:3
线程1:3
线程3:4
线程1:4
*/
实现Callable接口
- 覆写
call()
方法 - 有返回值*
package com.example.test.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CustomeThreadByImplCallable implements Callable<String> {
private static int count = 20;
@Override
public String call() throws Exception {
for(int i=count;i>0;i--) {
Thread.yield();
System.out.println(Thread.currentThread().getName()+":"+i);
}
return "test callable implements";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> callable = new CustomeThreadByImplCallable();
FutureTask<String> futureTask = new FutureTask<>(callable);
Thread mThread=new Thread(futureTask);
mThread.setName("线程3");
Thread mThread2=new Thread(futureTask);
mThread2.setName("线程2");
Thread mThread3=new Thread(futureTask);
mThread3.setName("线程1");
// for(int i =0;i<5;i++){
mThread.start();
mThread2.start();
mThread3.start();
// }
System.out.println(futureTask.get());
}
}
注意:
为什么运行的时候只打印了一个线程的结果?
为什么运行三次,得到的分别是线程1、线程2、线程3 这样的结果?
线程2:20
线程2:19
线程2:18
线程2:17
线程2:16
线程2:15
线程2:14
线程2:13
线程2:12
线程2:11
线程2:10
线程2:9
线程2:8
线程2:7
线程2:6
线程2:5
线程2:4
线程2:3
线程2:2
线程2:1
test callable implements
通过线程池启动多线程
通过Executor 的工具类可以创建三种类型的普通线程池:
FixThreadPool(int n); 固定大小的线程池
使用于为了满足资源管理需求而需要限制当前线程数量的场合。使用于负载比较重的服务器。
package com.example.test.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolTest {
public static void main(String[] args) {
//创建一个固定大小的线程池
ExecutorService ex = Executors.newFixedThreadPool(5);
/**
* 这里设置成小于5的数值,就只会有小于5的固定的线程执行
* 设置的大于5的数值,就会是有5个线程一起随机执行
* 不会出现大于5个线程运行的情况
*/
for(int i=0;i<30;i++) {
ex.submit(new Runnable() {
@Override
public void run() {
for(int j=0;j<5;j++) {
System.out.println("线程"+Thread.currentThread().getName()+"执行第"+j+"次");
}
}
});
}
//获取线程返回数据
ex.shutdown();
}
}
执行结果:
线程pool-1-thread-1执行第0次
线程pool-1-thread-5执行第0次
线程pool-1-thread-2执行第0次
线程pool-1-thread-3执行第0次
线程pool-1-thread-3执行第1次
线程pool-1-thread-4执行第0次
线程pool-1-thread-3执行第2次
线程pool-1-thread-2执行第1次
线程pool-1-thread-5执行第1次
线程pool-1-thread-1执行第1次
线程pool-1-thread-5执行第2次
线程pool-1-thread-2执行第2次
线程pool-1-thread-3执行第3次
线程pool-1-thread-4执行第1次
线程pool-1-thread-3执行第4次
线程pool-1-thread-2执行第3次
线程pool-1-thread-5执行第3次
线程pool-1-thread-1执行第2次
线程pool-1-thread-5执行第4次
线程pool-1-thread-2执行第4次
线程pool-1-thread-4执行第2次
线程pool-1-thread-1执行第3次
线程pool-1-thread-4执行第3次
线程pool-1-thread-1执行第4次
线程pool-1-thread-5执行第0次
线程pool-1-thread-4执行第4次
线程pool-1-thread-5执行第1次
线程pool-1-thread-5执行第2次
线程pool-1-thread-5执行第3次
线程pool-1-thread-5执行第4次
SingleThreadPoolExecutor :单线程池
需要保证顺序执行各个人物的场景
package com.example.test.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadPoolExecuor {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for(int i=0;i<6;i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
for(int j=0;j<5;j++) {
System.out.println("线程"+Thread.currentThread().getName()+"执行第"+j+"次");
}
}
});
}
//获取线程返回数据
executorService.shutdown();
}
}
运行结果:
线程pool-1-thread-1执行第0次
线程pool-1-thread-1执行第1次
线程pool-1-thread-1执行第2次
线程pool-1-thread-1执行第3次
线程pool-1-thread-1执行第4次
线程pool-1-thread-1执行第0次
线程pool-1-thread-1执行第1次
线程pool-1-thread-1执行第2次
线程pool-1-thread-1执行第3次
线程pool-1-thread-1执行第4次
线程pool-1-thread-1执行第0次
线程pool-1-thread-1执行第1次
线程pool-1-thread-1执行第2次
线程pool-1-thread-1执行第3次
线程pool-1-thread-1执行第4次
线程pool-1-thread-1执行第0次
线程pool-1-thread-1执行第1次
线程pool-1-thread-1执行第2次
线程pool-1-thread-1执行第3次
线程pool-1-thread-1执行第4次
线程pool-1-thread-1执行第0次
线程pool-1-thread-1执行第1次
线程pool-1-thread-1执行第2次
线程pool-1-thread-1执行第3次
线程pool-1-thread-1执行第4次
线程pool-1-thread-1执行第0次
线程pool-1-thread-1执行第1次
线程pool-1-thread-1执行第2次
线程pool-1-thread-1执行第3次
线程pool-1-thread-1执行第4次
CashedThreadPool(); 缓存线程池
当提交任务速度高于线程池中任务处理速度时,缓存线程池会不断的创建线程
适用于提交短期的异步小程序,以及负载较轻的服务器
package com.example.test.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CacheThreadPoolTest {
public static void main(String[] args) {
//创建一个缓存线程下池
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i=0;i<6;i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
for(int j=0;j<5;j++) {
System.out.println("线程"+Thread.currentThread().getName()+"执行第"+j+"次");
}
}
});
}
//获取线程返回数据
executorService.shutdown();
}
}
运行结果:
线程pool-1-thread-4执行第0次
线程pool-1-thread-2执行第0次
线程pool-1-thread-1执行第0次
线程pool-1-thread-3执行第0次
线程pool-1-thread-6执行第0次
线程pool-1-thread-6执行第1次
线程pool-1-thread-5执行第0次
线程pool-1-thread-6执行第2次
线程pool-1-thread-5执行第1次
线程pool-1-thread-3执行第1次
线程pool-1-thread-1执行第1次
线程pool-1-thread-2执行第1次
线程pool-1-thread-1执行第2次
线程pool-1-thread-4执行第1次
线程pool-1-thread-1执行第3次
线程pool-1-thread-2执行第2次
线程pool-1-thread-3执行第2次
线程pool-1-thread-5执行第2次
线程pool-1-thread-6执行第3次
线程pool-1-thread-6执行第4次
线程pool-1-thread-5执行第3次
线程pool-1-thread-3执行第3次
线程pool-1-thread-2执行第3次
线程pool-1-thread-1执行第4次
线程pool-1-thread-4执行第2次
线程pool-1-thread-2执行第4次
线程pool-1-thread-3执行第4次
线程pool-1-thread-5执行第4次
线程pool-1-thread-4执行第3次
线程pool-1-thread-4执行第4次
线程的状态
线程运行的几个方法
- Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。
- Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。
- t.join()/t.join(long millis),当前线程里调用其它线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程进入就绪状态。
- obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。
- obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
反射
注解
元注解
在java.lang.annotation包下,定义了6个注解类
- Documented
说明该注解将被包含在javadoc中 - Retention
定义注解的保留策略
@Retention(RetentionPolicy.SOURCE) //注解仅存在于源码中,在class字节码文件中不包含
@Retention(RetentionPolicy.CLASS) // 默认的保留策略,注解会在class字节码文件中存在,但运行时无法获得,
@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
首 先要明确生命周期长度 SOURCE < CLASS < RUNTIME ,所以前者能作用的地方后者一定也能作用。一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。
- Target
定义注解的作用目标
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
@Target(ElementType.TYPE) //接口、类、枚举、注解
@Target(ElementType.FIELD) //字段、枚举的常量
@Target(ElementType.METHOD) //方法
@Target(ElementType.PARAMETER) //方法参数
@Target(ElementType.CONSTRUCTOR) //构造函数
@Target(ElementType.LOCAL_VARIABLE)//局部变量
@Target(ElementType.ANNOTATION_TYPE)//注解
@Target(ElementType.PACKAGE) ///包
由于target注解的value是一个ElementType类型的数组,所以这个注解的值可以传数组方式, 如:@Target({ElementType.FIELD})
- Inherited
说明子类可以继承父类中的该注解 - Native
使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。对于 @Native 注解不常使用,了解即可 - Repeatable
允许在相同的程序元素中重复注解,在需要对同一种注解多次使用时,往往需要借助 @Repeatable 注解。Java 8 版本以前,同一个程序元素前最多只能有一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”
2个枚举类
- RetentionPolicy
public enum RetentionPolicy {
//此注解类型的信息只会记录在源文件中,编译时将被编译器丢弃,也就是说
//不会保存在编译好的类信息中
SOURCE,
//编译器将注解记录在类文件中,但不会加载到JVM中。如果一个注解声明没指定范围,则系统
//默认值就是Class
CLASS,
//注解信息会保留在源文件、类文件中,在执行的时也加载到Java的JVM中,因此可以反射性的读取。
RUNTIME
}
- ElementType
定义注解的作用范围:类,字段,局部变量
如何获取定义的注解中的值?
使用java的反射机制获取注解上的值
//获取类名的包名地址
printClass = Class.forName("com.lxp.demo.Schedules.TestThtread");
//java反射机制获取所有方法名
Method[] declaredMethods = printClass.getDeclaredMethods();
//遍历循环方法并获取对应的注解名称
for (Method declaredMethod : declaredMethods) {
String isNotNullStr = "";
// 判断是否方法上存在注解 MethodInterface
boolean annotationPresent = declaredMethod.isAnnotationPresent(MethodInterface.class);
if (annotationPresent) {
// 获取自定义注解对象
MethodInterface methodAnno = declaredMethod.getAnnotation(MethodInterface.class);
// 根据对象获取注解值
isNotNullStr = methodAnno.name();
}
list.add(new KeyValueDto(declaredMethod.getName(),isNotNullStr));
}
//等等
//在切面中获取注解中的描述信息
其他注解
- @RequestHeader
SpringMVC提供了@RequestHeader注解用于映射请求头数据到Controller方法的对应参数。
使用@RequestHeader注解与使用@RequestParam一样,在方法的形参前加上注解即可。
了解Request Header的内容,你可以访问W3C的网站
+ @RequestBody
@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的);GET方式无请求体,所以使用@RequestBody接收数据时,前端不能使用GET方式提交数据,而是用POST方式进行提交。在后端的同一个接收方法里,@RequestBody与@RequestParam()可以同时使用,@RequestBody最多只能有一个,而@RequestParam()可以有多个。
注:一个请求,只有一个RequestBody;一个请求,可以有多个RequestParam。
枚举
Java 枚举是一个特殊的类,一般表示一组常量
Java 枚举类使用 enum 关键字来定义,各个常量使用逗号 , 来分割
public enum Color {
RED,BLACK,GREEN;
}
枚举的构造方法
类拥有构造器,枚举是一种特殊的类,所以枚举也可以拥有自己的构造器。但与普通类的不同之处在于枚举的构造器不可以是public的,其原因在于该构造器是提供给枚举对象中的枚举项构造时使用的,它并不需要在枚举对象之外使用。
例如,如果希望枚举MyColor中的每个枚举项包含有相应的中文说明以及其对应的Color信息,则可以为MyColor增加一个包含有两个参数的构造器,并且在声明每一个枚举项时使用这个构造器进行构造
package com.example.test.annotation;
public enum MyColor {
RED("红色",Color.RED);//如果不声明下面的构造函数,这种写法会报错。
String desc;
Color color;
private MyColor(String desc, Color color){
this.desc = desc;
this.color = color;
}
}
欢迎各位看官指正!
特此说明:
- 该文章只做个人学习只用,不做商用。
- 该文章有有借鉴其他的文章,若有侵权请联系修正,谢谢!