java基础部分面试知识点总结

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();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值