java学习题目整理

2 篇文章 0 订阅
1 篇文章 0 订阅

一、java 基础

1.1 请你说明 String 和 StringBuffer 的区别

  • String类是final修饰的,所以String的值是无法改变的,对于已经存在的String对象的修改都是重新创建一个新的对象,然后把新的值保存进去,这样原来的对象就没用了,就要被垃圾回收,这也是要影响性能的。
  • tringBuffer对象代表的是一个内容可变的字符串,如果在程序中需要对字符串进行频繁的修改连接操作的话.使用StringBuffer性能会更高。

1.2 请你说明一下 int 和 Integer 有什么区别

  • int则是java的一种基本数据类型
  • Integer是int的包装类,Integer变量必须实例化后才能使用,而int变量不需要
  • Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值 。
  • Integer的默认值是null,int的默认值是0

1.3 数组(Array)和列表(ArrayList)的区别?什么时候应该使用 Array 而不是 ArrayList?

  • ArrayList想象成一种“会自动扩增容量的Array”
  • Array([]):最高效;但是其容量固定且无法动态改变;
  • ArrayList: 容量可动态增长;但牺牲效率;
  • 基于效率和类型检验,应尽可能使用Array无法确定数组大小时才使用ArrayList
    不过当你试着解决更一般化的问题时,Array的功能就可能过于受限。

1.4 什么是值传递和引用传递?(参数传递基本上就是赋值操作。)

  • 值传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的一个副本。因此,如果函数修改了该参数,仅改变副本,而原始值保持不变。

  • 引用传递意味着当将一个参数传递给一个函数时,函数接收的是原始值的内存地址,而不是值的副本。因此,如果函数修改了该参数,调用代码中的原始值也随之改变。

  • 在 Java 应用程序中永远不会传递对象,而只传递对象引用。因此是按引用传递对象。但重要的是要区分参数是如何传递的,这才是该节选的意图。Java 应用程序按引用传递对象这一事实并不意味着 Java 应用程序按引用传递参数。参数可以是对象引用,而 Java 应用程序是按值传递对象引用的。
    Java 应用程序中的变量可以为以下两种类型之一:引用类型或基本类型。当作为参数传递给一个方法时,处理这两种类型的方式是相同的。两种类型都是按值传递的;没有一种按引用传递。

    当传递给函数的参数不是引用时,传递的都是该值的一个副本(按值传递)。区别在于引用。在 C++ 中当传递给函数的参数是引用时,您传递的就是这个引用,或者内存地址(按引用传递)。在 Java 应用程序中,当对象引用是传递给方法的一个参数时,您传递的是该引用的一个副本(按值传递),而不是引用本身。
    Java 应用程序按值传递所有参数,这样就制作所有参数的副本,而不管它们的类型。

    @Setter
    @Getter
    @ToString
    public class Person {
    
    
        public String a;        //最大范围public
        protected String b;     //受保护类型
        String c;               //默认的访问权限
        private String d;       //私有类型
        
        @Test
        void addressTest(){
            Person person = new Person();
            person.setA("张三");
            System.out.println(person);
            getName(person);
            System.out.println(person);
    
        }
        void getName(Person p) {
            //如果此处按引用传递的话addressTest()最后一处输出应该是李四的那个对象。
            p=new Person();
            p.setA("李四");
            System.out.println(p);
    
        }
    }
    

1.5Java 支持的数据类型有哪些?什么是自动拆装箱?

  • 四类八种:byte、short、int、long,float、duble,boolean,char

  • 装箱就是自动将基本数据类型转换为包装类型;拆箱就是自动将包装类型转换为基本数据类型。

1.6 为什么会出现 4.0-3.6=0.40000001 这种现象?

  • 这种舍入误差的主要原因是:

​ 浮点数值采用二进制系统表示, 而在二进制系统中无法精确地表示分数 1/10。

​ 这就好像十进制无法精确地表示分数 1/3—样。

​ 如果在数值计算中不允许有任何舍入误差, 就应该使用 BigDecimal类

1.7java8 的新特性吗,请简单介绍一下

  • Lambda 表达式 − Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。

​ + 最简单的Lambda表达式可由逗号分隔的参数列表、**->**符号和语句块组成

  • 函数接口概念

​ + Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式。java.lang.Runnablejava.util.concurrent.Callable是函数式接口的最佳例子。在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解**@FunctionalInterface**(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义:

@FunctionalInterface
public interface Functional {
    void method();
}

不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。

@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();
    default void defaultMethod() {            
    }        
}

Lambda表达式作为Java 8的最大卖点,它有潜力吸引更多的开发者加入到JVM平台,并在纯Java编程中使用函数式编程的概念。如果你需要了解更多Lambda表达式的细节,可以参考官方文档

  • 方法引用 − 方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

​ + 方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有很多复杂的模板代码。

西门的例子中,Car类是不同方法引用的例子,可以帮助读者区分四种类型的方法引用。

public static class Car {
    public static Car create( final Supplier< Car > supplier ) {
        return supplier.get();
    }              
    public static void collide( final Car car ) {
        System.out.println( "Collided " + car.toString() );
    }
    public void follow( final Car another ) {
        System.out.println( "Following the " + another.toString() );
    }
    public void repair() {   
        System.out.println( "Repaired " + this.toString() );
    }
}

第一种方法引用的类型是构造器引用,语法是Class::new,或者更一般的形式:Class::new。注意:这个构造器没有参数。

final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );

第二种方法引用的类型是静态方法引用,语法是Class::static_method。注意:这个方法接受一个Car类型的参数。

cars.forEach( Car::collide );

第三种方法引用的类型是某个类的成员方法的引用,语法是Class::method,注意,这个方法没有定义入参:

cars.forEach( Car::repair );

第四种方法引用的类型是某个实例对象的成员方法的引用,语法是instance::method。注意:这个方法接受一个Car类型的参数:

final Car police = Car.create( Car::new );
cars.forEach( police::follow );
  • 默认方法 − 默认方法就是一个在接口里面可以有一个或多个实现的方法。

  • Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。

    • Java应用中最常见的bug就是空值异常。在Java 8之前,Google Guava引入了Optionals类来解决NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java 8也将Optional加入了官方库。

    Optional仅仅是一个容易:存放T类型的值或者null。它提供了一些有用的接口来避免显式的null检查,可以参考Java 8官方文档了解更多细节。

    接下来看一点使用Optional的例子:可能为空的值或者某个类型的值:

    Optional< String > fullName = Optional.ofNullable( null );
    System.out.println( "Full Name is set? " + fullName.isPresent() );        
    System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) ); 
    System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
    

    如果Optional实例持有一个非空值,则**isPresent()**方法返回true,否则返回false;**orElseGet()**方法,Optional实例持有null,则可以接受一个lambda表达式生成的默认值;map()方法可以将现有的Opetional实例的值转换成新的值;**orElse()方法与orElseGet()**方法类似,但是在持有null的时候返回传入的默认值。

    上述代码的输出结果如下:

    Full Name is set? false
    Full Name: [none]
    Hey Stranger!
    

    再看下另一个简单的例子:

    Optional< String > firstName = Optional.of( "Tom" );
    System.out.println( "First Name is set? " + firstName.isPresent() );        
    System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) ); 
    System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
    System.out.println();
    

    这个例子的输出是:

    First Name is set? true
    First Name: Tom
    Hey Tom!
    
  • Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。

    • 新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中。这是目前为止最大的一次对Java库的完善,以便开发者能够写出更加有效、更加简洁和紧凑的代码。Steam API极大得简化了集合操作
  • Date Time API − 加强对日期与时间的处理。

    • Java 8引入了新的Date-Time API(JSR 310)来改进时间、日期的处理。时间和日期的管理一直是最令Java开发者痛苦的问题。java.util.Date和后来的java.util.Calendar一直没有解决这个问题(甚至令开发者更加迷茫)。

    因为上面这些原因,诞生了第三方库Joda-Time,可以替代Java的时间管理API。Java 8中新的时间和日期管理API深受Joda-Time影响,并吸收了很多Joda-Time的精华。新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。新设计的API认真考虑了这些类的不变性(从java.util.Calendar吸取的教训),如果某个实例需要修改,则返回一个新的对象。

    我们接下来看看java.time包中的关键类和各自的使用例子。首先,Clock类使用时区来返回当前的纳秒时间和日期。Clock可以替代System.currentTimeMillis()TimeZone.getDefault()

    // Get the system clock as UTC offset 
    final Clock clock = Clock.systemUTC();
    System.out.println( clock.instant() );
    System.out.println( clock.millis() );
    

    这个例子的输出结果是:

    2014-04-12T15:19:29.282Z
    1397315969360
    

    第二,关注下LocalDateLocalTime类。LocalDate仅仅包含ISO-8601日历系统中的日期部分;LocalTime则仅仅包含该日历系统中的时间部分。这两个类的对象都可以使用Clock对象构建得到。

    // Get the local date and local time
    final LocalDate date = LocalDate.now();
    final LocalDate dateFromClock = LocalDate.now( clock );
    System.out.println( date );
    System.out.println( dateFromClock );
    
    // Get the local date and local time
    
    final LocalTime time = LocalTime.now();
    
    final LocalTime timeFromClock = LocalTime.now( clock );
    
    System.out.println( time );
    
    System.out.println( timeFromClock );
    

    上述例子的输出结果如下:

    2014-04-12
    2014-04-12
    11:25:54.568
    15:25:54.568
    

    LocalDateTime类包含了LocalDate和LocalTime的信息,但是不包含ISO-8601日历系统中的时区信息。这里有一些关于LocalDate和LocalTime的例子

    // Get the local date/time
    final LocalDateTime datetime = LocalDateTime.now();
    final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );
    System.out.println( datetime );
    System.out.println( datetimeFromClock );
    

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

    2014-04-12T11:37:52.309
    2014-04-12T15:37:52.309
    

    如果你需要特定时区的data/time信息,则可以使用ZoneDateTime,它保存有ISO-8601日期系统的日期和时间,而且有时区信息。下面是一些使用不同时区的例子:

    // Get the zoned date/time
    final ZonedDateTime zonedDatetime = ZonedDateTime.now();
    final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
    final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );
    System.out.println( zonedDatetime );
    System.out.println( zonedDatetimeFromClock );
    System.out.println( zonedDatetimeFromZone );
    

    这个例子的输出结果是:

    2014-04-12T11:47:01.017-04:00[America/New_York]
    2014-04-12T15:47:01.017Z
    2014-04-12T08:47:01.017-07:00[America/Los_Angeles]
    

    最后看下Duration类,它持有的时间精确到秒和纳秒。这使得我们可以很容易得计算两个日期之间的不同,例子代码如下:

    // Get duration between two dates
    final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
    final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );
    final Duration duration = Duration.between( from, to );
    System.out.println( "Duration in days: " + duration.toDays() );
    System.out.println( "Duration in hours: " + duration.toHours() );
    

    这个例子用于计算2014年4月16日和2015年4月16日之间的天数和小时数,输出结果如下:

    Duration in days: 365
    Duration in hours: 8783
    

    对于Java 8的新日期时间的总体印象还是比较积极的,一部分是因为Joda-Time的积极影响,另一部分是因为官方终于听取了开发人员的需求。如果希望了解更多细节,可以参考官方文档

    并行数组

    Java8版本新增了很多新的方法,用于支持并行数组处理。最重要的方法是parallelSort(),可以显著加快多核机器上的数组排序。下面的例子论证了parallexXxx系列的方法:

    package com.javacodegeeks.java8.parallel.arrays;
    import java.util.Arrays;
    import java.util.concurrent.ThreadLocalRandom;
    public class ParallelArrays {
        public static void main( String[] args ) {
            long[] arrayOfLong = new long [ 20000 ];        
            Arrays.parallelSetAll( arrayOfLong, 
            index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
            Arrays.stream( arrayOfLong ).limit( 10 ).forEach( 
                i -> System.out.print( i + " " ) );
            System.out.println();
            Arrays.parallelSort( arrayOfLong );        
            Arrays.stream( arrayOfLong ).limit( 10 ).forEach( 
            i -> System.out.print( i + " " ) );
            System.out.println();
        }
    }
    

    上述这些代码使用**parallelSetAll()方法生成20000个随机数,然后使用parallelSort()**方法进行排序。这个程序会输出乱序数组和排序数组的前10个元素。上述例子的代码输出的结果是:

1.8 你说明符号“==”比较的是什么?

  • ==可以比较基本数据类型和引用类型,基本数据类型比较的是数值,在比较引用类型时,除了比较数值外,还要比较引用地址,两者都相等时,结果才是true

1.9Object 若不重写 hashCode()的话,hashCode()如何计算出来的?

  • Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法直接返回对象的 内存地址。

1.10 为什么重写 equals 还要重写 hashcode?

  • hashcode

hashCode即散列码。散列码是用一个int值来代表对象,它是通过将该对象的某些信息进行转换而生成的。

Object类中默认的hashCode方法如下。

public native int hashCode();

这是一个本地方法,不同的虚拟机有不同的实现(具体实现自己看虚拟机源码哈)。Object默认的hashCode是根据对象的内存地址转化而来的,它是唯一的。
hashCode方法主要是为了给诸如HashMap这样的哈希表使用。

设计hashcode最重要的因素是:对同一个对象调用hachCode()应该产生同样的值(前提是对象的信息没有被改变)。

设计一个hashCode,它必须快,而且具有意义(使用有意义的字段来生成hashcode)。hashCode不需要唯一(默认的hashCode唯一),因此更应该关注它的速度,而不是唯一性。

由于在生成桶(桶指哈希桶,或哈希表的槽位)的下标前,hashcode还要做进一步处理,所以生成的hashCode范围不是很重要,是int就行。

好的hashCode()应该产生分布均匀的散列码。

  • equals

    hashCode并不需要唯一性,但equals必须严格地判断两个对象是否相同。

    正确的equals方法有如下特性:

    自反性:x.equals(x)一定返回true

    对称性:如果x.equals(y)为true,那么y.equals(x)也为true

    传递性:如果x.equals(y)为true、y.equals(z)为true,那么x.equals(z)也为true

    一致性:如果x和y中用于等价比较的信息没有改变,那么x.equals(y)无论调用多少次,结果都一致

    任何不是null的x,x.equals(null)一定返回false

  • equals与hashCode的相关规定

    之所以有规定,是为了使诸如HashMap这样的哈希表正常使用。具体规定如下:

    equals相等,hashcode一定相等。

    equals不等,hashcode不一定相等。(哈希冲突)

    hashcode不等,equals一定不等。

    hashcode相等,equals不一定相等。(哈希冲突)

    因此,如果我们重写了equals,那么必须重写hashCode,使其满足这些规定。当然,如果我们不把自定义对象当成HashMap的键来使用,那么自定义对象不重写equals和hashCode也是可以的

  • equals相等,hashCode一定相等
    因为HashMap是用equals判断键是否相等的,用反证法,如果两个键 equals相等,而hashcode不等的话,那么就无法保证通过hashcode计算的下标值相等,下标值不等也就意味着相等的两个键却 到了不同的值,这肯定是不对的

1.11 若对一个类不重写,它的 equals()方法是如何比较的?

  • 和==一样的比较方法,可以比较基本数据类型和引用类型,基本数据类型比较的是数值,在比较引用类型时,除了比较数值外,还要比较引用地址,两者都相等时,结果才是true

二、关键字

2.1Java 里面的 final 关键字是怎么用的?

  • 在Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。下面就从这三个方面来了解一下final关键字的基本用法。

    • 修饰类

    ​ 当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。

    • 修饰方法

    ​ inal修饰的方法表示此方法已经是“最后的、最终的”含义,亦即此方法不能被重写(可以重载多个final修饰的方法)

    ​ 此处需要注意的一点是:因为重写的前提是子类可以从父类中继承此方法,如果父类中final修饰的方法同时访问控制权限为private,将会导致子类中不能直接继承到此方法,因此,此时可以在子类中定义相同的方法名和参数,此时不再产生重写与final的矛盾,而是在子类中重新定义了新的方法。(注:类的private方法会隐式地被指定为final方法。)

    • 修饰变量

    ​ final成员变量表示常量,只能被赋值一次,赋值后值不再改变。

    当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。

2.2 谈谈关于 Synchronized 和 lock

类别synchronizedLock
存在层次Java的关键字,在jvm层面上是一个接口
锁的释放(死锁产生)1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁(在发生异常时候会自动释放占有的锁,因此不会出现死锁)在finally中必须释放锁,不然容易造成线程死锁(发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生)
锁的获取假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待分情况而定,Lock有多个锁获取的方式,大致就是可以尝试获得锁,线程可以不用一直等待(可以通过tryLock判断有没有锁)
锁的状态无法判断可以判断
锁的类型可重入 不可中断 非公平可重入 可判断 可公平(两者皆可)
性能少量同步大量同步
调度使用Object对象本身的wait 、notify、notifyAll调度机制可以使用Condition进行线程之间的调度
用法在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。一般使用ReentrantLock类做为锁。在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。
底层实现底层使用指令码方式来控制锁的,映射成字节码指令就是增加来两个指令:monitorenter和monitorexit。当线程执行遇到monitorenter指令时会尝试获取内置锁,如果获取锁则锁计数器+1,如果没有获取锁则阻塞;当遇到monitorexit指令时锁计数器-1,如果计数器为0则释放锁。底层是CAS乐观锁,依赖AbstractQueuedSynchronizer类,把所有的请求线程构成一个CLH队列。而对该队列的操作均通过Lock-Free(CAS)操作。
  • Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)
  • 在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
  • ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。

2.3 请你介绍一下 volatile?

  • 用法:

volatile关键字:当多个线程操作共享数据时,可以保证内存中的数据可见。用这个关键字修饰共享数据,就会及时的把线程缓存中的数据刷新到主存中去,也可以理解为,就是直接操作主存中的数据。所以在不使用锁的情况下,可以使用volatile。如下:

public  volatile boolean flag = false;
  • volatile和synchronized的区别:

volatile不具备互斥性(当一个线程持有锁时,其他线程进不来,这就是互斥性)。

volatile不具备原子性。

2.4 请你介绍一下 Syncronized 锁,如果用这个关键字修饰一个静态方法,锁住了什么?如果修饰成员方法,锁住了什么?

synchronized修饰静态方法以及同步代码块的synchronized (类.class)用法锁的是类,线程想要执行对应同步代码,需要获得类锁。
synchronized修饰成员方法,线程获取的是当前调用该方法的对象实例的对象锁。

2.5 什么是原子性

原子性

原子性就是操作不可再细分,而i++操作分为读改写三步,如下:

int temp = i;
i = i+1;
i = temp;

所以i++明显不是原子操作。上面10个线程进行i++时,内存图解如下:

img

看到这里,好像和上面的内存可见性问题一样。是不是加个volatile关键字就可以了呢?其实不是的,因为加了volatile,只是相当于所有线程都是在主存中操作数据而已,但是不具备互斥性。比如两个线程同时读取主存中的0,然后又同时自增,同时写入主存,结果还是会出现重复数据。

**使用原子变量改进i++问题:**原子变量用法和包装类差不多,如下:

 AtomicInteger i = new AtomicInteger();
 public int getI(){
     return i.getAndIncrement();
 }
2.6什么 CAS算法:

CAS算法是计算机硬件对并发操作共享数据的支持,CAS包含3个操作数:

  • 内存值V
  • 预估值A
  • 更新值B

当且仅当V==A时,才会把B的值赋给V,即V = B,否则不做任何操作。就上面的i++问题,CAS算法是这样处理的:首先V是主存中的值0,然后预估值A也是0,因为此时还没有任何操作,这时V=B,所以进行自增,同时把主存中的值变为1。如果第二个线程读取到主内存中的还是0也没关系,因为此时预估值已经变成1,V不等于A,所以不进行任何操作。

三.面向对象

3.1Java 中的方法覆盖(Overriding)和方法重载(Overloading)是什么意思?

覆盖就是一个类在实现某个接口或继承另一个类的时候,对被继承(实现)的类(接口)的方法内部进行了修改,强调方法名和参数一致

重载就是某个类中有一个方法有n个参数,同一个类中又增加了一个方法名一样的方法,但是参数的数量不是n(或者参数的数量是n,参数的类型不相同或者参数的顺序不同)、返回值不同,强调方法名一致,参数不一致或返回值不一致

3.2 如何通过反射获取和设置对象私有字段的值?

忽略访问权限修饰符的安全检查 setAccessible(true):暴力反射

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.junit.jupiter.api.Test;

import java.lang.reflect.Field;

@Setter
@Getter
@ToString
public class Person {


    public String a;        //最大范围public
    protected String b;     //受保护类型
    String c;               //默认的访问权限
    private String d;       //私有类型

    @Test
    public void reflect2() throws Exception {
        //0、获取Person的Class对象
        Class personClass = Person.class;


        //1、Field[] getFields()获取所有public修饰的成员变量
        Field[] fields = personClass.getFields();
        for(Field field : fields){
            System.out.println(field);
        }
        System.out.println("=============================");
        //2.Field getField(String name)
        Field a = personClass.getField("a");


        //获取成员变量a 的值 [也只能获取公有的,获取私有的或者不存在的字符会抛出异常]
        Person p = new Person();
        Object value = a.get(p);
        System.out.println("value = " + value);


        //设置属性a的值
        a.set(p,"张三");
        System.out.println(p);
    }

    @Test
    public void reflect3() throws Exception {
        Class personClass = Person.class;


        //Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符
        Field[] declaredFields = personClass.getDeclaredFields();
        for(Field filed : declaredFields){
            System.out.println(filed);
        }
        System.out.println("===================================");
        //Field getDeclaredField(String name)
        Field d = personClass.getDeclaredField("d");     //private String d;
        Person p = new Person();


        //Object value1 = d.get(p);    //会抛出异常
        //System.out.println("value1 = " + value1);    //对于私有变量虽然能会获取到,但不能直接set和get


        //忽略访问权限修饰符的安全检查
        p.setD("333");
        d.setAccessible(true);//暴力反射
        Object value2 = d.get(p);
        System.out.println("value2 = " + value2);
    }
}
  • (1)设置值 void set(Object obj, Object value)
  • (2)获取值 get(Object obj)

这里获取到person对象中私有变量d的值为“333”。

3.3 请说明内部类可以引用他包含类的成员吗,如果可以,有没有什么限制吗?

如果不是静态内部类,那没有什么限制。如果你把静态嵌套类当作内部类的一种特例,那在这种情况下不可以访问外部类的普通成员变量,而只能访问外部类中的静态成员,例如,下面的代码:

/**
 * Description 
 *
 * @Author blindore
 * @Date Created in 2021-07-01 16:39
 */
class Outer
{
    static int x;
    static class Inner
    {
        void test()
        {
            System.out.println(x);
        }
    }
}

3.4 当一个对象被当作参数传递给一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?

答:是值传递。
Java 编程语言只有值传递参数。当一个对象实例作为一个参数被传递到方法中时,参数的值就是该对象的引用一个副本。指向同一个对象,对象的内容可以在被调用的方法中改变,但对象的引用(不是引用的副本)是永远不会改变的。

Java参数,不管是基本类型还是引用类型,传递的都是副本(有另外一种说法是传值,但是说传副本更好理解吧,传值通常是相对传址而言)。

如果参数类型是基本类型,那么传过来的就是这个参数的一个副本,也就是这个原始参数的值,这个跟之前所谈的传值是一样的。如果在函数中改变了副本的值不会改变原始的值.

如果参数类型是引用类型,那么传过来的就是这个引用参数的副本,这个副本存放的是参数的地址。如果在函数中没有改变这个副本的地址,而是改变了地址中的 值,那么在函数内的改变会影响到传入的参数。如果在函数中改变了副本的地址,如new一个,那么副本就指向了一个新的地址,此时传入的参数还是指向原来的 地址,所以不会改变参数的值。

3.5 什么是泛型?

泛型将接口的概念进一步延伸,“泛型”的字面意思就是广泛的类型。类、接口和方法代码可以应用于非常广泛的类型,代码与它们能够操作的数据类型不再绑定在一起,同一套代码可以用于多种数据类型,这样不仅可以复用代码,降低耦合性,而且还提高了代码的可读性以及安全性。

使用泛型的好处
  • 代码复用:我们一套代码可以支持不同的类性。
  • 降低了耦合性:代码逻辑和数据类型之间分离,实现了解耦。
  • 更好的可读性:我们在使用集合的时候,定义了一个list 如List<String>,一看便知道这个一个存放String类型的list。
  • 更高的安全性:语言和程序设计的一个重要目标就是将bug消灭在摇篮里,能在写的时候消灭,就不要留在运行的时候。如我们定义一个List<String>这样的一个list。当我们往list里面放其他非String类型的数据时,我们的IDE(如Eclipse)就会报错提示。就算没有IDE。编译时,Java编译器也会提示,这称之为类型安全。这样就为程序设置了一道安全防护。同样的,使用泛型还可以省去使用普通对象时繁琐的强制类型转换。相反,使用普通对象,编译时并不会提示。假如传入的参数类型和最后强制类型转换的类型不一致。运行时就会出现ClassCastException,使用泛型则不会。

3.6 解释一下类加载机制,双亲委派模型,好处是什么?

img

  • Bootstrap(启动)类加载器:它负责加载最基础的$JAVA_HOME/jre/lib/rt.jar中的所有类,以及-Xbootclasspath参数中指定路径的类。它比较特殊,是由C++实现的,不是ClassLoader的子类,而是JVM自身的一部分。
  • Extension(扩展)类加载器:它负责加载Java中的一些扩展功能,包括$JAVA_HOME/jre/lib/ext/*.jar中的所有类,以及-Djava.ext.dirs参数中指定路径的类。
  • Application(应用)类加载器:它负责加载当前应用程序classpath中的类。
  • Custom(自定义)类加载器:用户自己通过继承ClassLoader类并覆写findClass()方法来加载用户指定的类。
  • 双亲委派整个过程分为以下几步:
    1. 假设用户刚刚摸鱼写的Test类想进行加载,这个时候首先会发送给应用程序类加载器AppCloassLoader;
    2. 然后AppClassLoader并不会直接去加载Test类,而是会委派于父类加载器完成此操作,也就是ExtClassLoader;
    3. ExtClassLoader同样也不会直接去加载Test类,而是会继续委派于父类加载器完成,也就是BootstrapClassLoader;
    4. BootstrapClassLoader这个时候已经到顶层了,没有父类加载器了,所以BootstrapClassLoader会在jdk/lib目录下去搜索是否存在,因为这里是用户自己写的Test类,是不会存在于jdk下的,所以这个时候会给子类加载器一个反馈。
    5. ExtClassLoader收到父类加载器发送的反馈,知道了父类加载器并没有找到对应的类,爸爸靠不住,就只能自己来加载了,结果显而易见,自己也不行,没办法,只能给更下面的子类加载器了。
    6. AppClassLoader收到父类加载器的反馈,顿时明白,原来爸爸虽然是爸爸,但是他终究不能管儿子的私事,所以这时候,AppClassLoader就自己尝试去加载。
    7. 结果,就这样成功了,走了一大圈,兜兜转转还是自己干。

这种双亲委派模式的好处,一个可以避免类的重复加载,另外也避免了java的核心API被篡改

3.7”static”关键字是什么意思?Java 中是否可以覆盖(override)一个 private 或者是 static 的方法?

​ static是表示静态的意思,它可用来修饰成员变量和成员函数,被静态修饰的成员函数只能访问静态成员,不能访问非静态成员。静态是随着类的加载而加载,因此可以直接用类进行访问。

覆盖又称为重写,重写就是子类中的方法和子类继承的父类中的方法一样(函数名、参数类型、参数、返回值类型),但子类的访问权限不要低于父类的访问权限。重写的前提是必须要继承,private修饰不支持继承,因此被私有的方法不能重写。静态的方法形式上是可以被重写的,即子类中可以重写父类中的静态方法,但实际上在内存的角度上静态方法是不可以被重写的。

3.8 列举你所知道的 Object 类的方法并简要说明。

Object()构造函数

clone()另存一个当前存在的对象。

equals(Object)确认两个对象是否相同。

hashCode()用于获取对象的哈希值,这个值的作用是检索

getClass()返回一个Class对象

toString()返回一个String对象,用来标识自己

notify()用于随机通知一个持有对象的锁的线程获取操作权限

notifyAll()用于通知所有持有对象的锁的线程获取操作权限

wait()用于让当前线程失去操作权限,当前线程进入等待序列

wait(long)用于设定下一次获取锁的距离当前释放锁的时间间隔

wait(long,int)用于设定下一次获取锁的距离当前释放锁的时间间隔

3.9 类和对象的区别

类是一类物种的定义,对象是类的具体实例。举例:

​ 这世界上不存在类,只有对象。狗类是一个抽象的概念,但具体的一条叫旺财的狗是一个对象。

3.10 Java中静态内部类和非静态内部类有什么区别?

内部类有以下几个主要的特点:

第一,内部类可以访问其所在类的属性(包括所在类的私有属性),内部类创建自身对象需要先创建其所在类的对象

public class TestInner {
    public static void main(String[] args) {
        new TestInner().new Inner().new Inner_2().print();
    }
    public class Inner {
        public class Inner_2 {
            void print(){
                System.out.println("展示一下内部类的实例化");
            }
            public class Inner_3 {

            }
        }
    }
}

第二,可以定义内部接口,且可以定义另外一个内部类实现这个内部接口

public class TestInner {
    public static void main(String[] args) {
        new TestInner().new Inner_5().inter();
    }
    class Inner_5 implements Inner_4{
        @Override
        public void inter() {
            System.out.println("内部类实现内部接口");
        }
    }
    public interface Inner_4 {
        void inter();
    }
}

第三,可以在方法体内定义一个内部类,方法体内的内部类可以完成一个基于虚方法形式的回调操作

public class TestInner {
    public static void main(String[] args) {
        TestInner testInner = new TestInner();
        Inner inner = testInner.new Inner();
        inner.getCar().paint();
    }

    public class Inner {
        public M_Car getCar() {
            class BMW extends M_Car {
                public void paint() {
                    System.out.println("BMW");
                }
            }
            return new BMW();
        }
    }
}
class M_Car {
    public void paint(){
        System.out.println("car");
    }
}

第四,内部类不能定义static元素,即使是

public static void main(String[] args) {

    }

也不可以

第五,内部类可以多嵌套(示例见第一个例子)

static内部类是内部类中一个比较特殊的情况,Java文档中是这样描述static内部类的一旦内部类使用static修饰,那么此时这个内部类就升级为顶级类。也就是说,除了写在一个类的内部以外,static内部类具备所有外部类的特性
通过这个例子我们发现,static内部类不仅可以在内部定义static元素,而且在构建对象的时候也可以一次完成。从某种意义上说,static内部类已经不算是严格意义上的内部类了。
与static内部类不同,内部接口自动具备静态属性,也就是说,普通类是可以直接实现内部接口的,看一个例子:

public class TestInner {
    public interface Inner_4 {
        void inter();
    }
}
class Inner_5 implements TestInner.Inner_4{
    @Override
    public void inter() {
        System.out.println("内部类实现内部接口");
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值