javase


1、java语言的特点

面向对象、多线程、跨平台、高效、安全可靠、网络编程

2、什么是字节码?

一个java文件,经过javac命令编译之后就变成了字节码的文件,字节码是由十六进制的值组成,jvm以字节为单位进行读取。java之所以可以一次编写,到处运行,主要是无论在什么平台,都可以编译生成字节码文件给jvm使用。

好处:java通过字节码的方式,在一定程度上解决了解释性语言执行效率低的问题,同时也保留了可移植性的特点。

3、java的数据类型

基本数据类型:byte、short、int、long、float、double、char、boolean

javase

引用数据类型:数组、类型、接口

4、访问修饰符

java通过访问修饰符来控制对变量、方法、类的访问,有四种类型。

  • private(私有):只能作用在当前类当中
  • default(默认,什么都不写):在同一个包内可见
  • protected(保护):作用在当前包和子类
  • public(公共):所有的都可以访问

5、break、continue和return

  • break:跳出所在的当前整个循环,到外层代码继续执行。

  • continue:退出本次循环

  • return:结束方法

6、final、finally和finalize

  • final是java的关键字:是java的关键字,可以修饰变量、方法和类
    • 修饰的变量不可变,必须初始化,通常称被修饰的变量为常量
    • 修饰的方法不能被重写,但是子类可以使用该方法
    • 修饰的类不能被继承
  • finally:作为异常处理的一部分,配合try catch 使用,finally块里面的代码最终都会被执行,无论是否出现异常,但是可以通过system.exit(0)可以阻断finally执行
  • finalize:是java.lang.Object里的方法,也就是说每个对象都有该方法,在垃圾回收的时候调用,只会调用一次。

7、static

static是java的关键字,用在变量、方法和代码块上,随着类的加载而加载,加载过程中完成内存的分配,是属于类的,在不用创建对象实例的情况下可以访问,在本类中可以直接访问,在其他类中可以通过类名.(变量名/方法名)来调用。

  • 静态变量只有一份被加载到内存中,被所有类共享
  • 静态方法中,不能访问实例变量和实例方法(因为还没有创建出来),可以访问静态变量和静态方法

8、java属性的执行顺序

1、父类静态变量和静态代码块(先声明的先执行);

2、子类静态变量和静态代码块(先声明的先执行);

3、父类的变量和普通代码块(先声明的先执行);

4、父类的构造函数;

5、子类的变量和普通代码块(先声明的先执行);

6、子类的构造函数。

9、面向对象和面向过程

面向对象:用代码来高度模拟现实世界。将现实世界的事物抽象成对象,将事物的特征和行为封装在一个类中

面向过程:分析出解决问题的步骤,然后用函数将这些步骤一步一步实现

面向对象的三大特征:

  • 封装:就是把事物封装成类,类内部的实现给隐藏起来了,外界只需要调用方法即可,不用知道怎么实现的。也可以控制可以被谁访问。提高了安全性和代码的组件化思想
  • 继承:子类继承父类的属性和方法,子类拥有父类的属性和方法,并且也可以拥有自己独有的属性何方法(进行扩展)。提高了代码的复用性,

关于继承有几个注意点:

  • 子类有自己的构造器,不能继承父类的构造器
  • 对于父类的私有变量和方法,子类只是拥有,但是不能访问
  • 实现继承后,子类初始化时必须先初始化父类(子类构造器第一行隐藏的代码:super();)

java是单继承:因为如果是多继承,多个父类有相同的属性和方法,子类不知道调用哪一个

一个类默认继承了Object类,要么间接继承了Object类(Object类是所有类的祖宗)

  • 多态:指父类中的属性和方法被子类继承后,可以有不同的数据类型或表现不同的行为。(继承的都是同一个类,但是子类都有自己的实现方式)

多态的实现方式:

父类类型   对象名 = new 子类构造器();
接口类型   对象名 = new 实现类构造器();

实现前提:

  • 必须有继承或者实现关系
  • 必须存在父类类型指向引用类型
  • 有方法重写

10、重载和重写的区别

重载(运行时多态:在编译时,无法确定调用哪个方法,只有在调用方法指定参数时才知道):是方法与方法之间,方法名相同, 参数列表不同。返回类型可以相同也可以不相同;(有参无参构造器)

重写(编译时多态):子类对父类的方法进行重写,方法名、参数列表、返回值都要相同。(override)

11、抽象类和接口的区别

抽象类:被abstract修饰的类叫抽象类;如果一个类中没有足够的信息来描述一个对象,那么这个类就叫做抽象类;

接口:被interface修饰的类,接口是更加彻底的抽象,接口体现的是规范思想,实现接口的子类必须重写完所有的抽象方法。

区别:

  • 抽象类只能被单继承,接口可以多实现

  • 抽象类有构造器,接口没有构造器

  • 抽象类的变量可以使用各种各样类型,接口的变量只能是由public static final修饰的常量

  • 抽象类可以有普通方法,接口只能是静态方法、默认方法和抽象方法

共同点:

  • 都有抽象方法
  • 都不能实例化(创建对象)

12、java创建对象的几种方式

  1. 通过new,调用构造函数创建对象
  2. 通过反射(调用newInstance方法来获取实例)
  3. 使用Object的clone方法
  4. 使用对象输入流ObjectInputStream的readObject方法读取序列化对象(反序列化)

13、什么是不可变对象?

不可变对象是指对象一旦创建出来,状态就不能更改,任何修改都会创建一个新的对象。因为其是不可变的,所以就不存在线程安全问题。

创建一个包含可变对象的不可变对象:String对象是不可变的,但是数组里面的对象是可变的

final String[] str=new String[]{对象1,对象2};

14、值传递和引用传递

值传递:就是基本类型的数据的传递,传递的是值的拷贝

引用传递:就是引用数据类型的传递,传递是是地址

15、==号和equals的区别

==号:如果比较的是基本类型,那么直接比较值是否相等;如果比较的是引用类型,比较的是引用类型的地址

euqals:比较两个对象,当没有重写equals方法之前,比较的是地址;重写equals方法后,比较的是内容是否相等

16、hashcode

hashcode的作用是获取哈希码(int类型的整数);哈希码的作用是获取对象在hash表中的索引位置,hashcode方法是Object方法,所有的对象都有。

哈希表存储的是key-value形式,所有根据key(索引)可以快速定位到对象

为什么要有hashcode:

以HashSet如何去重为例,HashSet会先计算对象的hash值来判断,如果hash值不同,那么对象就不重复;如果hash值相同,这时还要调用equals方法来判断两个对象的内容是否相同,如果内容相同,则说明是重复的,如果不同,则说明不是重复的。

17、为什么重写equals还要重写hashcode

因为单单只靠euqals不能判断两个对象是否相同

HashMap在put一个键值对时,会先根据键的hashCode和equals方法来同时判断该键在容器中是否已经存在,如果存在则覆盖,反之新建。所以如果我们在重写equals方法时,没有重写hashCode方法,那么hashCode方法还是会默认使用Object提供的原始方法,而Object提供的hashCode方法返回值是不会重复的(也就是说每个对象返回的值都不一样)。所以就会导致每个对象在HashMap中都会是一个新的键。就会有相同的键出现在map集合

18、String、StringBuffer、StringBuilder

String:是一个字符串对象

String str='';//直接创建的放在字符串常量池
String str1=new String();//通过new创建,在堆中产生一个新的对象

String的不可变性:

  • 因为保存字符串的数组被final修饰并且是私有的,string内部也没有暴露访问这个字符串数组的方法
private final cahr value[]
  • string类被final修饰,不能被继承,避免子类破坏的可能性

    StringBuffer和StringBuilder是可变的:

cahr value[]

安全性:

  • String(不可变)和StringBuffer(内部方法是synchronized修饰)是线程安全的
  • StringBuilder是不安全的

19、String为什么要设计成不可变性

  1. 便于实现字符串常量池

在写代码时,会使用大量的字符串,如果每次使用字符串都需要在堆中new一个对象,那么会造成极大的空间浪费,所以java会在堆中开辟一个字符串池,当初始化一个字符串变量时,如果字符串常量池存在相同的变量,就会直接返回已存在对象的引用,不会再创建新的对象。如果字符串是可变的,一旦改变了值,那么指向该对象的引用就会失效了。

  1. 线程安全:因为string是不可变的,其它的线程不能更改,就不会产生线程安全问题
  2. 提高效率

因为String是不可变的,所以它的hashcod是唯一的,在创建对象时可以放心的缓存,不需要重新计算。这也是Map集合喜欢用String来作为键的原因,处理速度更快。

20、包装类型

java为每一个基本类型的数据都引入了包装类型。

基本数据类型 位数 字节 默认值 取值范围 对应的包装类
byte 8 1 0 -128 ~ 127 Byte
short 16 2 0 -32768 ~ 32767 Short
int 32 4 0 -2147483648 ~ 2147483647 Integer
long 64 8 0L -9223372036854775808 ~ 9223372036854775807 Long
char 16 2 ‘u0000’ 0 ~ 65535 Character
float 32 4 0f 1.4E-45 ~ 3.4028235E38 Float
double 64 8 0d 4.9E-324 ~ 1.7976931348623157E308 Double
boolean 1 false true、false Boolean

区别:

  • 包装类型在没有赋值是默认为null,而基本类型都有默认值
  • 包装类型可以用于泛型,基本类型不能
  • 基本类型的局部变量存放在jvm栈帧的局部变量表,成员变量存在在堆中;包装类型是对象类型,存放在堆中

自动装箱:将基本类型转为包装类型

Integer a=10;//声明了一个Integer对象,底层调用Integer.valueOf(10)方法

自动拆箱:将包装类型转为基本类型

Integer a=10;
Sytsem.out.print(a--);//因为对象是不能计算的,使用先转为基本类型再计算

包装类型的缓存机制:

Byte、Short、Integer、Long默认创建了-128127的缓存数据,Character的范围是0127,

Boolean直接返回true、false

image.png

21、反射

指在程序在运行时,动态的获取类和对象的所有属性和方法;动态的获取信息和动态的调用方法

优点:能够动态的获取类的实例,提高灵活性

22、如何获取反射中的class对象

1、在编译阶段,Class.forName("类的路径")

2、类加载阶段:类名.class:一开始就知道要操作的类

3、运行阶段:对象名.getClass

4、如果是包装类型:包装类型.Type

23、反射的API

反射的api用来生成jvm中的类、接口或对象内部信息

Class类:反射的核心,用来获取类的实例

  1. static Class forName(String name) :返回指定类名 name 的 Class 对象
  2. Object newInstance() :调用缺省构造函数,返回该Class对象的一个实例

Field类:表示类的成员变量,可以设置或者获取类变量的值

Field getField(String name):返回此Class对象对应类的指定public Field

Field[] getDeclaredFields() :返回Field对象的一个数组

Method类:表示类的方法,可以获取类的方法信息或者执行方法

  1. Method getMethod(String name,Class … paramTypes) :返回一个Method对象,此对象的形参类型为paramType

Constructor类:表示类的构造方法

通过反射创建对象:

 public static void main(String[] args) throws Exception {
        //1、获取User类的Class对象
        Class<?> cls = Class.forName("Reflect.User");
        //2、通过public的无参构造创建实例
        Object o = cls.newInstance();//User{name = 小葱, age = 20}
        System.out.println(o);

        //3、通过public的有参构造创建实例  constructor对象就是:public User(String name)构造器
        Constructor<?> constructor = cls.getConstructor(String.class);
        Object o1 = constructor.newInstance("小聪");
        System.out.println(o1);//User{name = 小聪, age = 20}

        //通过private的有参构造创建实例
        Constructor<?> constructor1 = cls.getDeclaredConstructor(String.class, int.class);
        //暴破  使得反射可以访问private的构造器
        constructor1.setAccessible(true);
        Object o2 = constructor1.newInstance("小车车", 10);
        System.out.println(o2);//User{name = 小车车, age = 10}
        /*
        getConstructor:返回public的构造器对象
        
			class User{
					private String name="小葱";
					private int age=20;
	
					public User() {
					}
	
					private User(String name, int age) {
						this.name = name;
						this.age = age;
					}
					public User(String name) {
						this.name = name;
	
					}
				}
         */
    }

通过反射获取类的成员:

public class AccessProperty {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("Reflect.Student");
        Object o = cls.newInstance();//o 的运行类型是Student
        //使用反射得到age属性对象
        Field ageField = cls.getField("age");
        ageField.set(o,20);//通过反射来操作属性
        System.out.println(o);//Student{name = null, age = 20}
        System.out.println(ageField.get(o));//返回age属性的值

        //使用反射操作name属性对象
        Field nameField = cls.getDeclaredField("name");
        nameField.setAccessible(true);//可以访问私有
//        nameField.set(o,"小葱");
        nameField.set(null,"小葱");//因为name是static属性,因此 o 也可以写成null
        System.out.println(o);//Student{name = 小葱, age = 20}
    }
}
class Student{
    private static String name;
    public int age;
    public Student(){}

    public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }
}

通过反射调用方法:

public class AccessMethod {
    public static void main(String[] args) throws Exception {
        //得到类对象
        Class<?> cls = Class.forName("Reflect.Teacher");
        //创建对象
        Object o = cls.newInstance();
        //根据类的方法名得到方法对象
        Method hi = cls.getMethod("hi", String.class);
        hi.invoke(o,"xiaoc");//hixiaoc

        //私有方法对象
        Method say = cls.getDeclaredMethod("say", String.class, int.class);
        say.setAccessible(true);
        System.out.println(say.invoke(o, "小葱", 20));//小葱 20
        //在反射中 如果方法有返回值 统一返回Object类型
    }
}
class Teacher{
    private String name;
    public int age;

    public Teacher(){}

    private static String say(String str,int n){
        return str+" "+n;
    }
    public void hi(String s){
        System.out.println("hi"+s);
    }
}

24、泛型

将类型参数化,在编译时就确定号具体的参数。可以用在方法、接口和类上,称为泛型接口、泛型方法和泛型类。

核心思想:就是把出现泛型的地方全部替换为传进来的真实类型

好处:

  • 在编译阶段就确定参数的类型,从而不会出现类型转换异常
  • 消除了强制类型转换,使用泛型可以直接得到目标类型。

25、序列化与反序列化

序列化:把java对象转为字节序列(二进制流)的过程,以便在网络上传输或者保存在本地。

反序列化:把字节序列转为java对象的过程。

java对象是存储在jvm堆中的,如果jvm堆不存在了,那么java对象也消失了。所以使用序列化将对象保存在本地,需要时又从本地读取。

26、序列化实现方式

类必须实现如下两个接口之一,才能让类是可序列化的:

1、Serializable//这是一个标记接口 没有方法(常用)

2、Externalizable //需要实现方法

使用ObjectOutputStream

public static void main(String[] args) throws Exception {
        /*
            1、使用ObjectOutputStream 序列化 基本数据类型和一个Dog对象(name,age)并保存
         */
        String filePath="C://Users//xiaocong//Desktop//b.txt";
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));

        //序列化数据到 filePath
        oos.write(100);//int-->Integer(实现类序列化接口) 自动转换为包装类型
        oos.writeUTF("hello");//String
        //保存一个dog对象
        oos.writeObject(new Dog("大黄",10));
        oos.close();
    }

jvm在序列化时,会自动生成一个SerialVersionUID,然后与属性一起序列化,再进行持久化或网络传输。在反序列化时,jvm会再生成一个新的SerialVersionUID,然后将两个SerialVersionUID比较,如果相同,则反序列化成功,否则会报错。

如果显示指定了SerialVersionUID,jvm在创建SerialVersionUID时值是我们指定的。

如果不指定,会有什么问题?

如果我们写完类就不再修改,那就不会有问题。在实际开发中,类是不断迭代的,一旦类修改了,那么旧对象的反序列化就会失败。

如果有些字段不需要进行序列化,可以使用transient修饰。

静态变量不会被序列化,因为序列化是针对对象的,静态变量优先于对象创建

27、Error和Exception

在java中,所有异常都有一个共同的祖先,java.lang包下的Throwable类。Throwable有两个重要的子类,Exception(异常)和Error(错误)

image.png

Exception:程序本身可以处理的异常,可以通过try catch来捕获。

  • 异常由分为运行时异常和非运行时异常

运行时异常:又叫非受检查异常,表示在运行期间可能出现的异常,在编译阶段不会检查。

如:空指针异常、数组索引越界异常、算术错误异常、类转换异常

非运行时异常:又叫受检查异常,表示在编译期间就可以检查的异常

比如:类找不到异常、SQL异常

Error:属性程序无法处理的错误,一旦这类错误发生,程序会被终止。

声明异常:throws 作用在方法上,可以抛出多个异常

抛出异常:throw 用在方法内部,只能抛出一种异常。throw new 异常类型();

28、jvm是如何处理异常的

如果在一个方法中发生了异常,这个方法会创建一个异常对象,并传给jvm,该异常对象包含异常名,异常描述以及异常发生时应用程序的状态。可以有一系列方法的调用,最终才进入异常方法,这一系列方法会形成一个调用栈,jvm会顺着调用栈来查看是否有可以处理异常的代码,如果有,则调用异常处理代码。如果没有,jvm会在控制台打印出异常信息。

29、字节流如何转换为字符流

字节输入流转为字符输入流:InputStreamReader(解决字节流读取乱码的问题)

方法:public InputStreamReader(InputStream in, Charset cs) :指定编码方式

 public static void main(String[] args) throws IOException {
        String filePath="要读取的文件路径";
        //1、把FileInputStream  转为  InputStreamReader  并且指定编码
        InputStreamReader isr=new InputStreamReader(new FileInputStream(filePath), "GBK");
        //2、把  InputStreamReader 传入 BufferedReader
        BufferedReader br = new BufferedReader(isr);
        String s = br.readLine();
        System.out.println(s);
        //关闭外层流
        br.close();
    }

字节输出流转为为字符输出流:OutputStreamWriter

方法:public OutputStreamWriter(OutputStream out, String charsetName)

public static void main(String[] args) throws IOException {
        String fileName="要写入的文件路径";
        OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream(fileName),"gbk");
        BufferedWriter bw = new BufferedWriter(osw);
        bw.write("hello,world");
    }

30、什么是阻塞IO,非阻塞IO?

IO操作包括:对硬盘的读写,对socket读写以及外设的读写

当用户线程发起一个IO请求,操作系统会去查看要读取的数据是否准备就绪

  • 对于阻塞IO:如果数据没有准备就绪,则会一直等待,直到数据就绪
  • 对于非阻塞IO:如果数据没有就绪,则会返回一个标志信息告知用户线程当前要读的数据没有就绪。当数据就绪后,便将数据拷贝到用户线程。

31、BIO、NIO、AIO

BIO:

同步并阻塞,在服务器中的实现是一个连接一个线程。当有连接请求的时候,服务器会启动一个线程处理。如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

NIO:

同步并非阻塞,实现方式是一个请求一个线程,客户端发来的请求都会注册到多路复用器上,多路复用器查询到有连接IO请求时才会启动一个线程进行处理。(I/O多路复用通过一种机制,可以监视多个描述符,一旦某个描述符就绪,能够通知相应的进程/线程进行相应操作)

AIO:

异步并非阻塞,实现方式是一个有效请求一个线程,客户端的请求都是在操作系统完成之后,再通知服务器去启动线程处理。

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/279662.html

(0)
上一篇 2022年8月9日
下一篇 2022年8月9日

相关推荐

发表回复

登录后才能评论