Java基础部分
一、java基本语法
1.jvm、jre、jdk的区别
jvm java 虚拟机 执行java程序 跨平台
jdk包含jre,jdk里边有什么?
System.out.println(123);
jre包含jvm,里边有什么?
2.java的特性
跨平台性
安全性 (没有指针)
健壮性 (异常处理)
。。。
3.JVM执行过程
javac Hello.java 编译程序 ----》 Hello.class(字节码文件)
java Hello 运行程序(启动jvm)----》进入jvm—》类加载器会加载Hello.class文件—》字节码的校验器(检验程序是否有错误或异常)—》通过后进入解释器 --(把java程序翻译成操作系统能懂的指令)-》操作系统执行该指令
4.java的基本数据类型
java的数据类型分为: 基本数据类型、引用数据类型
八大基本数据类型:
数值型:
整数型: byte short int long
浮点型: float 、double
非数值型:
布尔型:boolean
字符型:char
引用数据类型: 数组、字符串、类的对象
bit 位
byte 1字节 1byte = 8bit
short 2字节 16位
int 4字节 32位 正负21亿多
long 8字节 64位
float 4字节 32位
double 8字节 64位
char 2字节 16位
boolean 不一定的,跟jvm的版本有关
int i = 3000000000;//超出了int型的范围
5.类型的转换
自动类型转换
从小的转大的就可以直接自动类型转换,没有进度的损失
强制类型转换
从大的转小的就可以直接强制类型转换,让程序员自己判断控制
short s = 100;
s = (short)(s + 10); //10默认是int型的,不同类型的值进行运算会向上转型
System.out.println(s);
6.运算符
& 与 &&的区别
int i = 2;
if(i > 2 && i++ > 3){ //&&输出2 &输出3
}
System.out.println(i);
位运算符
如何计算2*8的效率最高?
2<<3 等价于 2*8 ,哪个效率更高?
由于计算机存储就是以二进制数的形式存储的,所以位运算的效率是最高的,乘法还需要转换成加法再进行运算。
<< 符号位右移>> 无符号右移>>>
7.循环语句有哪些?
for循环 do while循环 while循环
循环语句的控制有几个关键字?
break continue return
break 直接结束循环
continue 结束本次循环
return 返回函数,结束所有的循环
public static void a(){
for(int i = 0 ; i < 10 ; i++){
for(int j = 0 ; j < 10 ;j++){
//if(j == 5) break;//结束所在这一层的循环
//if(j == 5) continue;//结束j==5的这一次循环
if(j == 5) return; //结束当前的方法的执行
}
System.out.println(i);
}
}
public static void main(String[] args){
a();//当遇到return的时候,该方法执行完毕
System.out.println(12);
}
8.值传递和引用传递
最大的区分就是形参的数据类型(基本数据类型和引用数据类型)
值传递:
public static void swap(int x , int y){
int temp;
temp = x;
x = y;
y = temp;
System.out.println(x + "," + y);
}
public static void main(String[] args){
int x = 1;
int y = 2;
swap(x , y);
System.out.println(x + "," + y);
}
public static void swap(String x , String y){
String temp;
temp = x;
x = y;
y = temp;
System.out.println(x + "," + y);
}
public static void main(String[] args){
String x = "a";
String y = "b";
swap(x , y);
System.out.println(x + "," + y);
}
引用传递:
public static void swap(int x[]){
int temp;
temp = x[0];
x[0] = x[1];
x[1] = temp;
System.out.println(x[0] + "," + x[1]);
}
public static void main(String[] args){
int x[] = {1 , 2};
swap(x);
System.out.println(x[0] + "," + x[1]);
}
判断依据看方法的参数数据类型: 假如是基本数据类型或者String类型,值传递,不会影响原来的值
假如是其他的引用数据类型,比如数组或者对象,会影响原来的值
9.String有哪些常用的方法?
char charAt(int index)
boolean contains(String s)
length()
substring()
int indexOf(‘a’)
lastIndexOf() “mypicture.jpg”
split() 通过指定分割符把字符串分割成数组
10.String创建对象的问题?
了解字符串常量池概念
了解String的值是不可改变的意思
String s = “a” + “b”; //创建了几个对象 “ab”,由于优化创建了一个对象"ab"
String s1 = “a”;
String s2 = “b”;
String s3 = s1 + s2; //“ab” ,实际创建了3个对象"a",“b”,“ab”
String s = new String(“abc”);//创建了几个对象
String s2 = new String(“abc”);
String s3 = “abc”; //上边3句总共就3个对象
11.String 、 StringBuffer 、 StringBuilder的区别
String的值是不可改变的
String s = “a”;
s = s + “b”; //s已经不是原来的s的地址了
for(int i = 0 ; i < 100 ;i++){
s = s + i; / /可能在常量池中产生了100个对象,而99个都是临时无用的对象
}
StringBuffer和StringBuilder的值是可以改变的
StringBuffer sb = new StringBuffer(“a”);
sb.append(“b”); //sb地址没有发生改变
for(int i = 0 ; i < 100 ;i++){
sb.append(“”+i); / /在堆中只有一个对象
}
StringBuffer和StringBuilder的区别?
定义基本一致
StringBuffer是线程安全的,效率低
StringBuilder是线程不安全的,效率高
12.== 与equals()区别
== 比较2个对象的地址是否相同
String s1 = “abc”;
String s2 = “abc”;
String s3 = new String(“abc”);
s1 == s2 //true,都指向同一个地址(常量池a的地址)
s1 == s3 //false,s1指向是常量池的a的地址,s3指向的堆中的对象的地址
Student st1 = new Student(1,“john”);
Student st2 = new Student(1,“john”);
st1 == st2 //s1和s2对象都是new出来的,地址一定不一样
st1.equals(st2) //相当于 st1 == st2 false
s1.equals(s3) //true ,由于String类重写了equals()方法
equals() 比较2个对象 ,Object类定义
Object类定义的,Object类是所有类(包括自定义的类)的父类,也就是说所有的类都有equals()
public boolean equals(Object obj) {
return (this == obj);
}
== 直接比较2个对象的地址
equals()在没有重写该方法的时候是继承Object的equals(),仍然是比较地址
当如String类重写了equals(),比较的是字符串的内容是否一致
public boolean a(String s){
if(s.equals("admin")) //可能会出现空指针异常,但s=null,程序会报错的
return true;
else return false;
}
注意以上代码:
第一个问题: 可能会出现空指针异常
public boolean a(String s){
if("admin".equals(s)) //就不会出现空指针异常,但s=null,返回值就是false
return true;
else return false;
}
第二个问题:学会代码逻辑的简化
public boolean a(String s){
return "admin".equals(s);
}
13.面向对象的三大特性
封装
public class Student{
private Integer id;
private String name;
private Integer age;
public void study(){
}
}
继承
单继承 extends(只能跟一个类)
一个类继承另外一个类,就拥有了该类除了私有的属性和方法之外的(继承)
扩展使用
extends继承类 implements实现接口(也可以理解成继承)
public class A implements B{} //A是B的子类,多态
多态
多态性指子类的对象可以赋值给父类的引用,但在运行是依然体现出来的子类的特征
实现的多态3点要求:
(1)继承
(2)方法重写
(3)父类引用子类对象
注意:
程序是分为编译和运行
编译的时候会检查父类是否定义了该方法,而运行的时候是调用的子类的方法
14.static关键字
能修饰什么?
修饰成员变量
修饰方法
修饰内部类
修饰代码块
public class Test{
private static String s = "a";//修饰成员变量---》类变量
static{ //静态代码块
System.out.println(123);
}
public static int a(){ //成员方法---》类方法
return 1;
}
public static class Inner{ //静态内部类
}
}
变成了static变量或者方法会怎么样?
成员变量—》类变量(共享变量)
成员方法–》类方法 可以不需要创建对象,直接使用类去调用该方法
Test t = new Test(); t.a();//可以这样用,但a()静态方法,可以不需要创建对象,直接用类调用
Test.a();//由于a()是静态的,可以直接使用类名调用该方法
package org.lanqiao;
public class Test02 {
static{
System.out.println("静态代码块1");
}
{
System.out.println("动态代码块2..");
}
public Test02(){
System.out.println("构造方法3");
}
public static void a(){
System.out.println("静态方法4...");
}
public static void main(String[] args) {
//Test02 t02 = new Test02();
//t02.a();
Test02.a();
}
}
静态代码块是先于调用构造方法(创建对象)!
对象的加载过程
java Test
类加载器把Test类加载JVM中,首先会先执行Test中的静态变量和静态代码块,然后才在堆中调用构造方法创建对象,初始化对象的属性和方法
执行过程:
静态变量 —》 静态代码块 --》动态代码块 —》构造方法 —》普通方法
静态代码块不管创建多少对象只会被执行一次,而动态代码块创建多少对象就执行多少次。
在继承中执行顺序是什么?
在创建子类对象时
父类的静态代码块—》子类的静态代码块—》父类动态代码块–》父类的无参构造方法(必须保证父类有无参的构造方法)–》子类的动态代码块–》子类的构造方法—》子类的普通方法
定义一个类的时候,假如用到了构造方法,一定要习惯的定义一个无参的构造方法,否则可能在假如有继承关系中会出问题。
15.方法的重载和重写的区别
编号 | 方法重载overloading | 方法重写overridding |
---|---|---|
1 | 方法重载用于提高程序的可读性。 | 方法重写用于提供已经由其超类提供的方法的特定实现。 |
2 | 方法重载在类内执行。 | 方法重写发生在具有IS-A(继承)关系的两个类中。 |
3 | 在方法重载的情况下,参数列表不同(参数个数、类型不同、顺序不同)。 | 在方法重写的情况下,参数必须相同。 |
4 | 方法重载是编译时多态性的例子。 | 方法重写/覆盖是运行时多态性的例子。 |
5 | 在java中,方法重载不能仅通过改变方法的返回类型来执行。方法重载中的返回类型可以相同或不同。 但是必须更改参数类型。 | 在方法重写/覆盖中返回类型必须相同或协变。 |
public class Test{
public void a(int a , float b){
}
public int a(float x, int y){
}
public void c(){
int x = 10;
int y = a(1,1.5f);
}
}
public class Father{
public String a(int x){
}
}
public class Son extends Father{
public int a(int b){
}
}
//方法的重写返回值类型要求: 子类的返回值类型只能跟父类的返回值类型相同或者是其子类
16.抽象类和接口的区别
jdk1.8之前的区别:
-
抽象类的定时使用abstract class ,接口的定义使用interface
-
抽象类和接口都不能实例化(都不能被new)
-
抽象类可以有构造方法,接口中不能有构造方法。
-
抽象类中可以有普通成员变量,接口中没有普通成员变量,只能定义常量final
-
抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。
-
抽象类中的抽象方法的访问类型可以是public,protected和默认类型,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。
-
抽象类中可以包含静态方法,接口中不能包含静态方法
-
抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型,并且默认即为public static final类型。
-
一个类可以实现多个接口,但只能继承一个抽象类
public interface A{
int b = 3;//可以,public final static省略
void a(); //可以,默认就是public abstract
}
public abstract class B{
public B(){
}
public void a(){
}
public abstract void b();
}
接口的主要作用是做行为的扩展。
应用:(1)多态使用,类是重,接口是轻量的,有些方法实现没有意义,所以可以使用抽象方法
(2)规范行为
17.jdk1.8的新特性
(1)接口中的默认方法和静态方法
在java里面,我们通常都是认为接口里面是只能有抽象方法,不能有任何方法的实现的,那么在jdk1.8里面打破了这个规定,引入了新的关键字default,通过使用default修饰方法,可以让我们在接口里面定义具体的方法实现,如下。
public interface NewCharacter {
public void test1();
default void test2(){
System.out.println("我是新特性1");
}
}
那这么定义一个方法的作用是什么呢?为什么不在接口的实现类里面再去实现方法呢?
其实这么定义一个方法的主要意义是定义一个默认方法,也就是说这个接口的实现类实现了这个接口之后,不用管这个default修饰的方法,也可以直接调用,如下。
public class NewCharacterImpl implements NewCharacter{
@Override
public void test1() {
}
public static void main(String[] args) {
NewCharacter nca = new NewCharacterImpl();
nca.test2();
}
}
所以说这个default方法是所有的实现类都不需要去实现的就可以直接调用,那么比如说jdk的集合List里面增加了一个sort方法,那么如果定义为一个抽象方法,其所有的实现类如arrayList,LinkedList等都需要对其添加实现,那么现在用default定义一个默认的方法之后,其实现类可以直接使用这个方法了,这样不管是开发还是维护项目,都会大大简化代码量。
(2)Lambda 表达式
Lambda表达式是jdk1.8里面的一个重要的更新,这意味着java也开始承认了函数式编程,并且尝试引入其中。
首先,什么是函数式编程,引用廖雪峰先生的教程里面的解释就是说:函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
简单的来说就是,函数也是一等公民了,在java里面一等公民有变量,对象,那么函数式编程语言里面函数也可以跟变量,对象一样使用了,也就是说函数既可以作为参数,也可以作为返回值了,看一下下面这个例子。
//这是常规的Collections的排序的写法,需要对接口方法重写
public void test1(){
List<String> list =Arrays.asList("aaa","fsa","ser","eere");
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.compareTo(o1);
}
});
for (String string : list) {
System.out.println(string);
}
}
//这是带参数类型的Lambda的写法
public void testLamda1(){
List<String> list =Arrays.asList("aaa","fsa","ser","eere");
Collections.sort(list, (Comparator<? super String>) (String a,String b)->{
return b.compareTo(a);
}
);
for (String string : list) {
System.out.println(string);
}
}
//这是不带参数的lambda的写法
public void testLamda2(){
List<String> list =Arrays.asList("aaa","fsa","ser","eere");
Collections.sort(list, (a,b)->b.compareTo(a)
);
for (String string : list) {
System.out.println(string);
}
可以看到不带参数的写法一句话就搞定了排序的问题,所以引入lambda表达式的一个最直观的作用就是大大的简化了代码的开发,像其他一些编程语言Scala,Python等都是支持函数式的写法的。当然,不是所有的接口都可以通过这种方法来调用,只有函数式接口才行,jdk1.8里面定义了好多个函数式接口,我们也可以自己定义一个来调用,下面说一下什么是函数式接口。
(3)函数式接口
定义:“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。jdk1.8提供了一个@FunctionalInterface注解来定义函数式接口,如果我们定义的接口不符合函数式的规范便会报错。
@FunctionalInterface
public interface MyLamda {
public void test1(String y);
//这里如果继续加一个抽象方法便会报错
/public void test1();
//default方法可以任意定义
default String test2(){
return "123";
}
default String test3(){
return "123";
}
//static方法也可以定义
static void test4(){
System.out.println("234");
}
}
看一下这个接口的调用,符合lambda表达式的调用方法。
MyLamda m = y -> System.out.println("ss"+y);
(4)方法与构造函数引用
jdk1.8提供了另外一种调用方式::,当 你 需 要使用 方 法 引用时 , 目 标引用 放 在 分隔符::前 ,方法 的 名 称放在 后 面 ,即ClassName :: methodName
。例如 ,Apple::getWeight
就是引用了Apple类中定义的方法getWeight。请记住,不需要括号,因为你没有实际调用这个方法。方法引用就是Lambda表达式(Apple a) -> a.getWeight()
的快捷写法,如下示例。
//先定义一个函数式接口
@FunctionalInterface
public interface TestConverT<T, F> {
F convert(T t);
}
测试如下,可以以::形式调用。
public void test(){
TestConverT<String, Integer> t = Integer::valueOf;
Integer i = t.convert("111");
System.out.println(i);
}
此外,对于构造方法也可以这么调用。
//实体类User和它的构造方法
public class User {
private String name;
private String sex;
public User(String name, String sex) {
super();
this.name = name;
this.sex = sex;
}
}
//User工厂
public interface UserFactory {
User get(String name, String sex);
}
//测试类
UserFactory uf = User::new;
User u = uf.get("ww", "man");
这里的User::new就是调用了User的构造方法,Java编译器会自动根据UserFactory.get方法的签名来选择合适的构造函数。
(5)Date Api更新
1.8之前JDK自带的日期处理类非常不方便,我们处理的时候经常是使用的第三方工具包,比如commons-lang包等。不过1.8出现之后这个改观了很多,比如日期时间的创建、比较、调整、格式化、时间间隔等。这些类都在java.time包下。比原来实用了很多。
6.1 LocalDate/LocalTime/LocalDateTime
LocalDate为日期处理类、LocalTime为时间处理类、LocalDateTime为日期时间处理类,方法都类似,具体可以看API文档或源码,选取几个代表性的方法做下介绍。
now相关的方法可以获取当前日期或时间,of方法可以创建对应的日期或时间,parse方法可以解析日期或时间,get方法可以获取日期或时间信息,with方法可以设置日期或时间信息,plus或minus方法可以增减日期或时间信息;
6.2TemporalAdjusters
这个类在日期调整时非常有用,比如得到当月的第一天、最后一天,当年的第一天、最后一天,下一周或前一周的某天等。
6.3DateTimeFormatter
以前日期格式化一般用SimpleDateFormat类,但是不怎么好用,现在1.8引入了DateTimeFormatter类,默认定义了很多常量格式(ISO打头的),在使用的时候一般配合LocalDate/LocalTime/LocalDateTime使用,比如想把当前日期格式化成yyyy-MM-dd hh:mm:ss的形式:
LocalDateTime dt = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
System.out.println(dtf.format(dt));
(6)Stream流
定义:流是Java API的新成员,它允许我们以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。就现在来说,我们可以把它们看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理,也就是说我们不用写多线程代码了。
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。
流的操作类型分为两种:
-
Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
-
Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。
在对于一个 Stream 进行多次转换操作 (Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个 for 循环里把所有操作都做掉的总和吗?其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。
构造流的几种方式
// 1. Individual values
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();