JAVA基础面试题汇总及JAVA8新特性

本文介绍了JAVA中的基础概念,包括Object类的结构和方法,封装、继承和多态的概念,以及泛型的使用。还讨论了wait/notify机制、异常处理、反射机制、注解的原理,以及并发中的锁策略。此外,还涵盖了内部类、字符串处理和时间日期的操作等内容。
摘要由CSDN通过智能技术生成

JAVA基础

Object类

new 一个Object多少字节?

16字节。

8字节的mark word。4字节的指针(指向类元信息地址,应该是class?)。

默认开启指针压缩,所以是4字节,关闭的话为8字节。

内存对齐填充了最后四个字节,希望字节数是8的倍数。

有哪些方法

hashcode,equals,toString,wait,notify

为什么wait/notify在Object内而不是Thread

因为等待/通知机制设计是为了解决线程间通信的。

线程通信是否可执行,是由资源决定的,而非线程。

一个线程使用完毕某个资源,别的线程才能使用。

线程知道自己是因为要获取哪个资源而被阻塞,关注的是资源,而不在乎是因为谁(具体哪个线程)。

封装,继承,多态

封装。将数据或函数等集合在一个类中。隐藏实现的细节。隔离性。安全。

继承。子类拥有父类方法,减少代码重复量。

多态。一个接口,多个实现类,可以做出不同的反应。编译类型和运行类型不同。

拆箱,装箱

Integer i = 10;  //装箱
int n = i;   //拆箱
  • Integer i = 10 等价于 Integer i = Integer.valueOf(10)
  • int n = i 等价于 int n = i.intValue();

为什么要有包装类

包装类extends Object。体现Java面向对象的思想。

集合类泛型必须是一个Object的子类,因此泛型不能是int等基本数据类型。

接口和抽象类有什么共同点和区别?

共同点

  • 都不能被实例化。
  • 都可以包含抽象方法。
  • 都可以有默认实现的方法(Java 8 可以用 default 关键字在接口中定义默认方法)。

区别

  • 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
  • 一个类只能继承一个类,但是可以实现多个接口。
  • 接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。

final

  1. final int a = 10; 修饰成员/局部变量,表示常量,不可修改
  2. final static double PAI_Value=3.14; 修饰静态变量,相当于全局常量
  3. final Student student = new Student(); 修饰对象,表示对象的指针不可修改,但student.id=1仍可以修改。不能修改的是指针,而不是指向的具体值,不能 student = student2
  4. final class A{} 修饰类,表示该类不可被继承,抽象类,接口都不能被final修饰
  5. final void function(){}修饰方法,表示不可以被重写,可以被重载
总结:
final 修饰类,不可被继承
修饰方法,不可被重写
修饰变量,不可被修改,常量

abstract:

  1. abstract修饰的类,不可生成实例
  2. 抽象类可以没有抽象方法,但抽象方法所属的类一定是抽象类
  3. 不能和private static共同修饰方法

interface:

  1. 接口是抽象方法和常量值的定义的集合

  2. 只能有抽象方法,不能有非抽象方法(这在抽象类是允许的),没有方法体

  3. 默认public修饰,可以改用为protected,但不能是其他

  4. 接口中的所有成员变量都默认是由public static final修饰的(静态常量)。

    接口中的所有方法都默认是由public abstract修饰的。

== 和 equals() 的区别

== 对于基本类型和引用类型的作用效果是不同的:

  • 对于基本数据类型来说,== 比较的是值。
  • 对于引用数据类型来说,== 比较的是对象的内存地址。

JAVA只有值传递,引用数据类型存储的是内存地址的值,所以本质完全相同,就是在比较值

Objectequals() 方法:

public boolean equals(Object obj) {
     return (this == obj);
}

若没有重写 equals() 方法,相当于==。比如String就重写了,所以equals比较的是值,而不是内存地址是否相同。重写equals方法一定要重写hashcode方法。避免两个equals的对象,放入到map中,经过hashcode成为了两个。

String, StringBuffer and StringBuilder

1. 可变性

  • String 不可变
  • StringBuffer 和 StringBuilder 可变

2. 线程安全

  • String 不可变,因此是线程安全的
  • StringBuilder 不是线程安全的
  • StringBuffer 是线程安全的,内部使用 synchronized 进行同步
String字节数

在String中,一个英文字符占1个字节;

而中文字符根据编码的不同所占字节数也不同。

  • 在UTF-8编码下,一个中文字符占3个字节。(包括中文标点符号)
  • 而使用GBK编码时一个中文字符占2个字节。

/n 两个字节

统计字符数用codePointCount,像𝄞这个字符,在char数组里长这样\uD834\uDD1E,用length计算是=2,而中文字符=1,所以没有过于特殊的字符情况下length不会错,但也有例外。

string B;
B.codePointCount(0,B.length())
String底层
private final char value[]; //1.8是一个字符数组
// jdk9 改成了byte数组

final修饰,因此不可变,是指针不可变。当然数组内的字符是可以改变的。

super关键字

  • super(a,b) 调用父类的构造方法

  • super.func()调用父类的成员

  • 调用super()必须写在子类构造方法的第一行, 否则编译不通过

  • 浅拷贝:拷贝的只是指针

  • 深拷贝:再生成一个相同的对象B,指向B

大坑

自定义注解?注解原理?泛型原理?异常深入?

泛型

List<String> list = new ArrayList<String>();
List list = new ArrayList();
// list中只能放String, 不能放其它类型的元素 

泛型类(接口一样)

class Notepad<K,V>{       // 此处指定了两个泛型类型  
    private K key ;     // 此变量的类型由外部决定  
    private V value ;   // 此变量的类型由外部决定  
} 
public class GenericsDemo09{  
    public static void main(String args[]){  
        Notepad<String,Integer> t = null ;        // 定义两个泛型类型的对象  
        t = new Notepad<String,Integer>() ;       // 里面的key为String,value为Integer  
    }  
}
// 类后加尖括号,内部放泛型
class Info<T extends Number>{
    // 此处表示泛型只能是数字类型 可以通过这种手段进行泛型上下边界的限制
    // extends表示T必须是E本身或者是E的子类
}
<? super E>
    // 此处表示泛型必须是E本身或者是E的父类

泛型方法

必须在返回值前边加一个<T>,来声明这是一个泛型方法

public <T> T function(Class<T> c){}
// Class<T>表示泛型的具体类型 c可以用来创建泛型类的对象(反射 c.newInstance)
// 就是你调用这个泛型方法,首先得指明泛型是什么具体类型(此时才知道了泛型具体是什么)
// 可以用Class.forName("完全类名")来表示泛型

注解

  • @Deprecated:表示代码被弃用

  • @SuppressWarnings:表示关闭编译器警告信息 有参数,直接用(all)吧

元注解: 是针对 public @interface Annotation {} 自己实现注解时用到的基础注解

  1. @targert 表示可以修饰什么内容
  2. @Retention & @RetentionTarget 表示注解在它所修饰的类中可以被保留到何时
  3. @Inherited 表示被该注解修饰的类 的子类 会一起继承该注解

反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。特点:动态获取信息。

Class.newInstance()
    // 通过反射,调用无参构造方法,生成对象
    //使用反射机制给属性赋值
        Class studentClass = Class.forName("javase.reflectBean.Student");
        Object obj = studentClass.newInstance();// obj就是Student对象。(底层调用无参数构造方法)
        // 获取no属性(根据属性的名称来获取Field)
        Field noField = studentClass.getDeclaredField("no");
        noField.set(obj, 22222);
     //使用反射机制调用方法
        Class userServiceClass = Class.forName("javase.reflectBean.UserService");
        // 创建对象
        Object obj = userServiceClass.newInstance();
        // 获取Method
        Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
        Object resValues = loginMethod.invoke(obj, "admin", "123");//注:方法返回值是void 结果是null

Class类

是一个真实存在的类,表示运行时的类。java类运行,生成.class文件。

获取class对象

可通过类名.class、对象.getClass()Class.forName("类名")等方法获取class对象

Constructor类

代表一个构造方法。

   //取得指定带int和String参数构造函数,该方法是私有构造private
        Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class);
        //由于是private必须设置可访问
        cs2.setAccessible(true);
        //创建user对象
        User user2= (User) cs2.newInstance(25,"hiway2");
        System.out.println("user2:"+user2.toString());
//getConstructor() 是只返回public修饰的
//getDeclaredConstructor 返回所有,包括private
//加s返回构造器数组

Field类

代表一个成员变量。API与Constructor类似

    Class<?> clazz = Class.forName("reflect.Student");
        //获取指定字段名称的Field类,注意字段修饰符必须为public而且存在该字段,
        // 否则抛NoSuchFieldException
        Field field = clazz.getField("age");
//获取当前类所字段(包含private字段),注意不包含父类的字段
        Field fields2[] = clazz.getDeclaredFields();
// 有Declared,就不包括继承的字段,且可以拿到所有修饰包括private
// 没有Declared,只能拿到public修饰的
ageField.set(对象实例,成员变量的具体值);

Field scoreField = clazz.getDeclaredField("score");
//设置可访问,score是private的
scoreField.setAccessible(true);
scoreField.set(st,88);

Method类

代表一个方法。

getDeclaredMethod/getDeclaredMethods方法只能获取当前类的方法;

getMethods方法获取Method对象时,会把父类的方法也获取到。

// invoke方法,执行该method,参数是 (对象,method参数)
Class clazz = Class.forName("reflect.Circle");
//创建对象
Circle circle = (Circle) clazz.newInstance();

//获取指定参数的方法对象Method
Method method = clazz.getMethod("draw",int.class,String.class);

//通过Method对象的invoke(Object obj,Object... args)方法调用
method.invoke(circle,15,"圈圈");

反射执行流程

异常

Throwable 是 Java 语言中所有错误与异常的超类。Error是无法处理的,Exception才是程序可以处理的。

自定义异常

自定义异常类(xxxException)继承RuntimeException,在捕获异常的地方

全局异常处理类

类上加@RestControllerAdvice注解 方法中指定捕获的类@ExceptionHandler(xxxException.class)

/**
 * 全局异常处理类
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 自定义异常
     **/
    @ExceptionHandler(CustomException.class)
    public Result customException(CustomException e) {
        return Result.failed(e.getCode(),e.getMessage(),e.getDate());
    }

    @ExceptionHandler(HttpServerErrorException.class)
    public Result httpServerErrorException(HttpServerErrorException e) {
        return Result.failed(503,e.getMessage());
    }
    @ExceptionHandler(Exception.class)
    public Result bindException(Exceptione) {
        return Result.failed(500,"参数校验异常" + e.getMessage());
    }
}

受检查异常和不受检查异常

受检查异常:是你必须要捕获的异常,否则无法通过编译。

不受检查异常:你可以捕获或者不捕获。只能打印日志

并发

synchronized关键字和Lock的实现类都是悲观锁

  • 悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。
  • 乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
   // synchronized用在普通方法上,默认的锁就是this,当前实例
    public synchronized void method() {}
   // synchronized用在静态方法上,默认的锁就是当前所在的Class类,所以无论是哪个线程访问它,需要的锁都只有一把
    public static synchronized void method() {}
   // 同步代码块形式——锁为this,两个线程使用的锁是一样的,线程1必须要等到线程0释放了该锁后,才能执行
        synchronized (this) { //同步代码块 }
   //锁Class对象  所有线程需要的锁都是同一把
        synchronized(SynchronizedObjectLock.class){}

math.round(-11.5)=-11 原因:四舍五入的原理是在参数上加0.5 然后进行下取整。

访问修饰符

  • private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
  • default (即缺省,什么也不写,不使用任何关键字): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
  • protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
  • public : 对所有类可见。使用对象:类、接口、变量、方法

内部类

  • 匿名内部类必须继承一个抽象类或者实现一个接口。
  • 匿名内部类不能定义任何静态成员和静态方法。
  • 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
  • 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
new/接口{
  //匿名内部类实现部分
}.方法

为什么java只有值传递?

Java方法传参,都是对所传变量进行拷贝,对基本数据类型来讲,拷贝的是实际数值,对引用数据类型来讲拷贝的是引用地址;

Java内存伪共享

伪共享:变量A和B,同处于一块CPU cache line中,线程A需要用变量A,线程B需要用变量B,但因为读取都是读一整个line的内容,因此对A和B的修改会导致另一个线程也要修改,降低效率,本来没有共享内存,但却伪共享了

@Contented修饰类的某个成员变量x,代表x会被放在另外的cache line中,避免伪共享。

获取当前时间

LocalDateTime
 LocalDateTime dateTime = LocalDateTime.now();
 String nowTime = dateTime.format(DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss"));

try

try catch finally捕获代码段的异常。

finally 即使在try catch中执行了 return操作,也会执行。

finally会先保存try尝试返回的值,然后运行finally代码块,如果finally内有return,会直接返回,无视try尝试返回的值。try的返回值又分为如下两种情况:

int num = 1;
try{
    return num;
}finally{
    num = 2;
}
//无法修改num的值,返回值仍然是1
//因为是基本数据类型,try备份的返回值是数据本身

-------------------------------------------------

String str = "123";
try{
    return str; //备份好str的地址值后,就去执行finally,然后才返回出去
}finally{
    str = "456";  //通过地址值去修改数据
}
//可以修改数据,return的地址值对应的数据是"456"
//地址就可以修改指向的对象

当然,JVM宕机了,finally块也不会执行。

Intern方法

String.intern()

返回字符串的引用,如果不在常量池就放到常量池,然后取出常量池中的String的引用。

JAVA8新特性

函数式编程

Stream中常用方法如下:

  • stream(), parallelStream()
  • filter()
  • findAny() findFirst()
  • sort
  • forEach void
  • map(), reduce()
  • flatMap() - 将多个Stream连接成一个Stream
  • collect(Collectors.toList())
  • distinct, limit
  • count
  • min, max, summaryStatistics

stream并不会修改数据源,也就是不会影响你传入的list。

比如执行filter,会生成一个新的stream

asList方法
String[] atp = {"Nadal", "Djokovic",  "Wawrinka"};  
List<String> players =  Arrays.asList(atp);  
foreach方法打印
players.forEach((player) -> System.out.print(player + "; "));  
filter&Predicate
// 根据条件过滤
public static void filter(List names, Predicate condition) {
    names.stream().filter((name) -> (condition.test(name))).forEach((name) -> {
        System.out.println(name + " ");
    });
}
// 输出以J开头的字符串
List languages = Arrays.asList("Java", "Scala", "C++", "Haskell", "Lisp");
filter(languages, (str)->str.startsWith("J"));
拼接多个Predicate
Predicate<String> startsWithJ = (n) -> n.startsWith("J");
Predicate<String> fourLetterLong = (n) -> n.length() == 4;
names.stream()
    .filter(startsWithJ.and(fourLetterLong))
    .forEach((n) -> System.out.print(n) );
// .and拼接
Predicate细节
支持 .and   /  .or拼接条件
    
//  Predicate.negate()方法,取反
    Predicate<String> startWithA = x -> x.startsWith("A");
    List<String> collect = list.stream()
                .filter(startWithA.negate())
        // 筛出不以A开头的字符串
                .collect(Collectors.toList());    
map

map是将原元素映射成新元素,抛弃旧元素(不是K-V形式那种)

// 转成大写
List<String> list3=list1.stream().map(s->s.toUpperCase).collect(Collectors.toList());
//                                   String::toUpperCase 也可以

// 将对象的list转成了 string的list
List<Student> list1=Arrays.asList(new Student("古力娜扎",18),new Student("迪丽热巴",19))List<String> list3=list1.stream().map(s->s.getName()).collect(Collectors.toList());
collect

再将流转回某个集合

// list
.collect(Collectors.toList());
// set
.collect(Collectors.toSet());
// map
.collect(Collectors.toMap(Product::getId, Product::getName));
// 分组 groupingBy
Map<String, List<Product>> groupListMap = prodList.stream().collect(Collectors.groupingBy(Product::getCategory));
// 拼接 joining
String nameJoin = prodList.stream().map(Product::getName).collect(Collectors.joining(","));
// 计数 counting
Long count = prodList.stream().collect(Collectors.counting());
flatmap&of 拼接stream
List<Integer> result= Stream.of(Arrays.asList(1,3),Arrays.asList(5,6))
    // 两个流
    .flatMap(a->a.stream()).collect(Collectors.toList());
// return 1,3,5,6
distinct

去重

List<Long> likeTidList = likeDOs.stream().map(LikeDO::getTid)
                .distinct().collect(Collectors.toList());
count

计数

persons.stream().filter(p -> p.getAge() > 18).count();
min/max
Person a = lists.stream().max(Comparator.comparing(t -> t.getId())).get();
sorted

排序

 .sorted((p, p2) -> (p.getFirstName().compareTo(p2.getFirstName())))
limit
 .limit(3) //只取前三个,同sql limit0,3
reduce

reduce方法来计算集合的累加和。

reduce做的是:从前两个元素开始,进行某种操作(这里进行的是加法操作)后,返回一个结果,然后再拿这个结果跟第三个元素执行同样的操作,以此类推,直到最后的一个元素。

   Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
                .reduce((a, b) -> {
                    return a + b;
                })
parallel

利用ForkJoinPool.commonPool-worker来工作,实现并行计算。

  Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
                .parallel()
                .reduce((a, b) -> {
                    return a + b;
                })
                .ifPresent(System.out::println);

上面这个例子它的输出结果如下:

ForkJoinPool.commonPool-worker-1: 3 + 4 = 7
ForkJoinPool.commonPool-worker-4: 8 + 9 = 17
ForkJoinPool.commonPool-worker-2: 5 + 6 = 11
ForkJoinPool.commonPool-worker-3: 1 + 2 = 3
ForkJoinPool.commonPool-worker-4: 7 + 17 = 24
ForkJoinPool.commonPool-worker-4: 11 + 24 = 35
ForkJoinPool.commonPool-worker-3: 3 + 7 = 10
ForkJoinPool.commonPool-worker-3: 10 + 35 = 45
//    而不用parallel 会按部就班的计算,如下:
main: 1 + 2 = 3
main: 3 + 3 = 6
main: 6 + 4 = 10
main: 10 + 5 = 15
main: 15 + 6 = 21
main: 21 + 7 = 28
main: 28 + 8 = 36
main: 36 + 9 = 45
45   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值