1、java语言的特点
面向对象、多线程、跨平台、高效、安全可靠、网络编程
2、什么是字节码?
一个java文件,经过javac命令编译之后就变成了字节码的文件,字节码是由十六进制的值组成,jvm以字节为单位进行读取。java之所以可以一次编写,到处运行
,主要是无论在什么平台,都可以编译生成字节码文件给jvm使用。
好处:java通过字节码的方式,在一定程度上解决了解释性语言执行效率低的问题,同时也保留了可移植性的特点。
3、java的数据类型
基本数据类型:byte、short、int、long、float、double、char、boolean
引用数据类型:数组、类型、接口
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创建对象的几种方式
- 通过new,调用构造函数创建对象
- 通过反射(调用newInstance方法来获取实例)
- 使用Object的clone方法
- 使用对象输入流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为什么要设计成不可变性
- 便于实现字符串常量池
在写代码时,会使用大量的字符串,如果每次使用字符串都需要在堆中new一个对象,那么会造成极大的空间浪费,所以java会在堆中开辟一个字符串池,当初始化一个字符串变量时,如果字符串常量池存在相同的变量,就会直接返回已存在对象的引用,不会再创建新的对象。如果字符串是可变的,一旦改变了值,那么指向该对象的引用就会失效了。
- 线程安全:因为string是不可变的,其它的线程不能更改,就不会产生线程安全问题
- 提高效率
因为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
21、反射
指在程序在运行时,动态的获取类和对象的所有属性和方法;动态的获取信息和动态的调用方法
优点:能够动态的获取类的实例,提高灵活性
22、如何获取反射中的class对象
1、在编译阶段,Class.forName("类的路径")
2、类加载阶段:类名.class
:一开始就知道要操作的类
3、运行阶段:对象名.getClass
4、如果是包装类型:包装类型.Type
23、反射的API
反射的api用来生成jvm中的类、接口或对象内部信息
Class类:反射的核心,用来获取类的实例
- static Class forName(String name) :返回指定类名 name 的 Class 对象
- Object newInstance() :调用缺省构造函数,返回该Class对象的一个实例
Field类:表示类的成员变量,可以设置或者获取类变量的值
Field getField(String name):返回此Class对象对应类的指定public Field
Field[] getDeclaredFields() :返回Field对象的一个数组
Method类:表示类的方法,可以获取类的方法信息或者执行方法
- 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(错误)
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