解释下什么是面向对象?面向对象和面向过程的区别?
JVM:将字节码文件转成具体系统平台的机器指令。 JRE:JVM+Java语言的核心类库。 JDK:JRE+Java的开发工具
java.lang.Object#clone
protected native Object clone() throws CloneNotSupportedException;
public class Person {
@Override
public Person clone() {
Person person = null;
try {
person = (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return person;
}
}
short s1 = 1;
// 会报错 s1 + 1运算时会自动提升表达式的类型,所以结果是int型, 所以需要将计算结果强转为short类型即s1 = (short) (s1 + 1);
// s1 = s1 + 1;
s1 = (short) (s1 + 1);
// 不会报错 += 是java语言规定的运算符,java编译器会对它进行特殊处理,因此可以正确编译。
s1 +=1;
switch可作用于char、byte、short、int及包装类上(4用)
switch不能可作用于long、double、float、boolean及包装类上(4不用)
jdk1.7后switch可作用于String上
// switch可作用于char byte short int
byte season4byte = 3;
char season4char = 3;
short season4short = 3;
int seasonint = 3;
// switch可作用于char byte short int对应的包装类
Byte season4Byte = 3;
Character season4Character = 3;
Short season4Short = 3;
Integer season4Integer = 3;
// switch不可作用于long double float boolean,包括他们的包装类
long season4long = 3;
double season4double = 3;
float season4float = 3f;
boolean season4boolean = false;
// 1.7之后可以作用在String上
String season4String = "";
String strSeason;
switch (season4Byte) {
case 1:
strSeason = "Spring";
break;
case 2:
strSeason = "Summer";
break;
case 3:
strSeason = "Fall";
break;
case 4:
strSeason = "Winter";
break;
default:
strSeason = "四季中没有这个季节";
break;
}
System.out.println("strSeason:" + strSeason);
final 是用来修饰类、方法、变量和参数的关键字;
finally 是 Java 中保证重点代码一定要被执行的一种机制;
finalize 是 Object 类中的一个方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收的,但其执行“不稳定”,且有一定的性能问题,已经在 JDK 9 中被设置为弃用的方法了。
hashCode()相同,equals不一定相同
equals不同,hashCode一定不相同
因为不同对象的hashCode 可能相同;但hashCode 不同的对象一定不相等,所以使用hashCode 可以起到快速初次判断对象是否相等的作用。
&和&&都可以做逻辑运算符号,但&&又叫短路运算符。 因为当第一个表达式的值为false的时候,则不会再计算第二个表达式;而&则不管第一个表达式是否为真都会执行两个表达式。 另外&还可以用作位运算符,当&两边的表达式不是Boolean类型的时候,&表示按位操作。
java中只有值传递,没有引用传递
形参:方法列表中的参数
实参:调用方法时实际传入到方法列表的参数(实参在传递之前必须初始化)
值传递:传递的是实参的副本(更准确的说是实参引用的副本,因为形参接受的是对象的引用)
引用传递:传递的是内存地址
public static void main(String[] args) {
// 实参
int num = 1;
// num 没有被改变 基本类型存储在栈里面,main方法栈里有一个num = 1,foo方法栈里存了一个副本num = 1;后来foo栈里面的改成了100,不会影响main方法中的
foo(num);
String str = "ABC";
foo(str); // str 也没有被改变
StringBuilder sb1 = new StringBuilder("iphone");
foo1(sb1); // sb 被改变了,变成了"iphone4"。
/*
* main方法栈有有个sb2 指向堆中的StringBuilder("iphone")对象
* 将main栈中的sb2的副本传递给foo2中的形参builder,builder指向堆中的StringBuilder("iphone")对象(与main是同一个对象)
* foo2栈中的builder指向StringBuilder("ipad")对象
* main栈中的sb2不会受影响
* 如果是引用传递main中的sb2会收到影响
*/
StringBuilder sb2 = new StringBuilder("iphone");
foo2(sb2); // sb 没有被改变,还是 "iphone"
System.out.println("num:" + num);//num:1
System.out.println("str:" + str);//str:ABC
System.out.println("sb1:" + sb1.toString());//sb1:iphone4
System.out.println("sb2:" + sb2.toString());//sb2:iphone
}
//第一个例子:基本类型 value为形参
static void foo(int value) {
value = 100;
}
//第二个例子:没有提供改变自身方法的引用类型
static void foo(String text) {
text = "windows";
}
/*
* 是否说明java支持引用传递呢? 不支持
* StringBuilder builder传递的仅仅是builder本身的值(即实参引用的副本)
*/
static void foo1(StringBuilder builder) {
builder.append("4");
}
//第四个例子:提供了改变自身方法的引用类型,但是不使用,而是使用赋值运算符。
static void foo2(StringBuilder builder) {
builder = new StringBuilder("ipad");
}
-1
有两种方式:
浅克隆:只复制基本类型的数据,引用类型的数据只复制了引用的地址,引用的对象并没有复制,在新的对象中修改引用类型的数据会影响原对象中的引用(引用类型:新老对象指向同一个对象)。
深克隆:是在引用类型的类中也实现了clone,是clone的嵌套,复制后的对象与原对象之间完全不会影响(手动调用引用类型的对象的clone方法)。
使用序列化也能完成深复制的功能:对象序列化后写入流中,此时也就不存在引用什么的概念了,再从流中读取,生成新的对象,新对象和原对象之间也是完全互不影响的。
@Data
public class Yyy implements Cloneable {
int id;
@Override
protected Yyy clone() throws CloneNotSupportedException {
return (Yyy) super.clone();
}
public Yyy(int id) {
this.id = id;
}
}
浅克隆
public class Xxx implements Cloneable {
private int age;
private Yyy yyy;
@Override
public Xxx clone() throws CloneNotSupportedException {
// 只克隆了基本类型,引用类型和原对象指向同一个堆中的对象
return (Xxx) super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Xxx xxx = new Xxx();
xxx.setAge(1);
xxx.setYyy(new Yyy(1));
Xxx clone = xxx.clone();
clone.getYyy().setId(2);
System.out.println("xxx = " + xxx);
System.out.println("clone = " + clone);
}
}
深克隆
public class Xxx implements Cloneable {
private int age;
private Yyy yyy;
@Override
public Xxx clone() throws CloneNotSupportedException {
Xxx clone = (Xxx) super.clone();
// 给引用类型重新赋值 调用引用类型的 clone方法
clone.setYyy(this.getYyy().clone());
return clone;
}
public static void main(String[] args) throws CloneNotSupportedException {
Xxx xxx = new Xxx();
xxx.setAge(1);
xxx.setYyy(new Yyy(1));
Xxx clone = xxx.clone();
clone.getYyy().setId(2);
System.out.println("xxx = " + xxx);
System.out.println("clone = " + clone);
}
}
xxx = Xxx(age=1, yyy=Yyy(id=1))
clone = Xxx(age=1, yyy=Yyy(id=2))
序列化:(方便在磁盘上存储或者在网络上传输)把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
利用ObjectOutputStream和ObjectInputStream序列化和反序列化时一定要实现Serializable接口,否则会报NotSerializableException
异常
利用fastjson序列化字符串时不实现Serializable不会报异常
@Data
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
// private static final long serialVersionUID = 2L;
public static int id;
private String name;
private int age;
private Pet pet;
private transient Car car;
private int height;
public static void main(String[] args) {
serializePerson();
deserializePerson();
}
private static void serializePerson() {
Person person = new Person();
person.setName("张三三");
person.setAge(18);
person.setPet(new Pet("大黄"));
person.setCar(new Car("奥迪"));
Person.id = 1;
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.txt"))) {
out.writeObject(person);
System.out.println("序列化完成");
} catch (Exception e) {
e.printStackTrace();
}
}
private static void deserializePerson() {
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.txt"))) {
Person readObject = (Person) in.readObject();
System.out.println("readObject = " + readObject);
System.out.println(Person.id);
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Data
class Car {
private String name;
public Car(String name) {
this.name = name;
}
}
@Data
class Pet implements Serializable {
private String name;
public Pet(String name) {
this.name = name;
}
}
序列化完成
readObject = Person(name=张三三, age=18, pet=Pet(name=大黄), car=null, height=0)
1
说明:
把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中; 在网络上传送对象的字节序列。
限定通配符包括两种:
表示类型的上界,格式为:<? extends T>,即类型必须为T类型或者T子类
表示类型的下界,格式为:<? super T>,即类型必须为T类型或者T的父类
List<? extends T> src 只能读,获取到的元素时T类型或者子类型,List<? super T> dest只写,写进去的元素时T类型或者T类型的子类型(上限怎么理解??? List的类型为T或者T的父类,所以你存T或者T的子类是都可以的),读又写List list不限定类型
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
}
非限定通配符:类型为,可以用任意类型来替代。
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
Java中的动态代理是一种在运行时生成代理对象的机制。它允许在不预先编写具体代理类的情况下创建代理对象,并且代理对象可以在运行时拦截并处理对目标对象的方法调用。
动态代理的应用场景
PersonServiceImpl target = new PersonServiceImpl();
// UserInterface接口的代理对象
// Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
Object proxy = Proxy.newProxyInstance(PersonService.class.getClassLoader(), new Class[]{PersonService.class}, (proxy1, method, args1) -> {
System.out.println("before...");
Object result = method.invoke(target, args1);
System.out.println("after...");
return result;
});
PersonService userService = (PersonService) proxy;
userService.addPerson(new Person("张三三"));
PersonServiceImpl target = new PersonServiceImpl();
// 通过cglib技术 /ɪnˈhɑːnsə(r)/ 增强器
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersonServiceImpl.class);
// 定义额外逻辑,也就是代理逻辑
enhancer.setCallbacks(new Callback[]{(MethodInterceptor) (o, method, objects, methodProxy) -> {
System.out.println("before...");
Object result = methodProxy.invoke(target, objects);
System.out.println("after...");
return result;
}});
// 动态代理所创建出来的UserService对象
PersonServiceImpl personService = (PersonServiceImpl) enhancer.create();
// 执行这个userService的test方法时,就会额外会执行一些其他逻辑
personService.addPerson(new Person("张三三"));
一个指向父类对象的引用变量
因为Sting类型在java中用的最多,所以做了特殊处理,为了提升性能,性能提升了,但是创建了很多对象,怎么破?常量池
Java中的字符串(String)被设计为不可变的主要有以下几个原因:
综上所述,将字符串设计为不可变的是为了性能优化、安全性、线程安全和哈希值缓存等方面的考虑。这是Java语言设计的一项重要决策。
综上所述,当字符串需要频繁修改时,应优先使用StringBuilder。如果在多线程环境下需要对字符串进行修改,应使用StringBuffer以保证线程安全性。而当字符串不需要修改时,或者需要在多个线程之间共享时,应使用String,以保证安全性。
不一样,String str = “i”;是把值放到了常量中,而String str = new String(“i”);是将值放到了堆内存中。
String s = "hello string ";
s.length();
s.trim();
s.contains("");
s.startsWith("");
s.endsWith("");
s.toLowerCase();
s.toUpperCase();
s.replace("","");
s.replaceFirst("","");
s.indexOf("");
s.lastIndexOf("");
s.substring(3);
s.split(" ");
s.matches("");
s.charAt(1);
s.intern();
s.toCharArray();
String.join(",","1","2");
String.format("hello %s","zss");
String.valueOf(1);
通过字面量赋值创建字符串(如:String s=”hi”),会先在常量池中查找是否存在相同的字符串,若存在,则直接指向该字符串;若不存在,则在常量池中生成一个字符串,再将引用指向该字符串。
通过new String创建字符串,在堆上创建一个,同时在常量池创建一个值相同的对象,但是这两个对象互不相干,如果常量池里已经有了同样的值的对象,只会在堆里新建对象
常量字符串和变量拼接时或者变量与变量拼接时会调用stringBuilder.append()在堆上创建新的对象,而不会同时在常量池里新建对象
String s = new String("a") + new String("b");
约等于
StringBuilder sb = new StringBuilder();
sb.append("a").append("b");
// toString()只会在堆上创建对象("ab"),new String("ab") 会在堆上和常量池都创建
String s = sb.toString();
调用字符串对象的 intern() 方法时,intern方法会先去常量池找,如果存在,指向常量池中的,如果不存在,在常量池中生成一个对原字符串的引用
字面量+字面量在编译期间就优化成了常量
String s = new String("a") + new String("b");
String intern = s.intern();
System.out.println(s == intern);// true
String str1 = "哈哈";
String str2 = str1 + "呵呵";
String str3 = "哈哈呵呵";
System.out.println(str2 == str3);// false
String s1 = "abc";
String s2 = "a";
String s3 = "bc";
String s4 = s2 + s3;
System.out.println(s1 == s4);// false
注意这个与上一个对比
String s1 = "abc";
final String s2 = "a";
final String s3 = "bc";
String s4 = s2 + s3;
// 因为final变量在编译后会直接替换成对应的值,所以实际上等于s4=”a”+”bc”,而这种情况下,编译器会直接合并为s4=”abc”
System.out.println(s1 == s4);//true
String s = new String("abc");
String s1 = "abc";
String s2 = new String("abc");
System.out.println(s == s1.intern());// false
System.out.println(s == s2.intern());// false
System.out.println(s1 == s2.intern());// true
String a = "a";
String b = "b";
String ab = a+b;
String ab2 = a+b;
String Iab = ab.intern();
String Iab2 = ab2.intern();
// ab与ab2都在堆里
System.out.println(ab==ab2);// false
// 常量池中没有"ab"即String ab = a+b;也等价于sb.append("a").append("b");
System.out.println(Iab==ab);// true
System.out.println(Iab2==ab);// true
你到底懂了吗
public class StringDemo {
public static void main(String[] args) {
m7();
}
private static void m1() {
String a = "a";
String b = "b";
String s = a + b;
String intern = s.intern();
System.out.println(s == intern);
}
private static void m2() {
String s = new String("a") + new String("b");
String intern = s.intern();
System.out.println(s == intern);
}
private static void m3() {
String a = "a";
String ab1 = a + "b";
String ab2 = "ab";
System.out.println(ab1 == ab2);
}
private static void m4() {
String a = "a";
String b = "b";
String ab1 = a + b;
String ab2 = "ab";
System.out.println(ab1 == ab2);
}
private static void m5() {
final String a = "a";
String ab1 = a + "b";
String ab2 = "ab";
System.out.println(ab1 == ab2);
}
private static void m6() {
String ab1 = "ab";
String ab2 = new String("ab");
String ab3 = new String("ab");
System.out.println(ab2 == ab1.intern());
System.out.println(ab2 == ab3.intern());
System.out.println(ab2.intern() == ab3.intern());
System.out.println(ab1 == ab3.intern());
}
private static void m7() {
String a = "a";
String b = "b";
String ab1 = a + b;
String ab2 = a + b;
System.out.println(ab1 == ab2);
System.out.println(ab1 == ab1.intern());
System.out.println(ab2 == ab2.intern());
System.out.println(ab1 == ab2.intern());
System.out.println(ab1.intern() == ab2.intern());
}
}
可以 ,final 修饰的是一个引用变量,那么这个引用始终只能指向这个对象,但是这个对象内部的属性是可以变化的。
常见的字节流
FileInputStream
try (FileInputStream inputStream = new FileInputStream("input.txt")) {
int data;
while ((data = inputStream.read()) != -1) {
System.out.println((char) data);
}
} catch (IOException e) {
e.printStackTrace();
}
BufferedInputStream
try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream("input.txt"))) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
System.out.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}
FileOutputStream
try (FileOutputStream outputStream = new FileOutputStream("output.txt")) {
String data = "Hello, World!";
byte[] bytes = data.getBytes();
outputStream.write(bytes);
} catch (IOException e) {
e.printStackTrace();
}
BufferedOutputStream
try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream("output.txt"))) {
String data = "Hello, World!";
byte[] bytes = data.getBytes();
outputStream.write(bytes);
} catch (IOException e) {
e.printStackTrace();
}
常见的字符流
FileReader
try (FileReader reader = new FileReader("example.txt")) {
int character;
while ((character = reader.read()) != -1) {
System.out.println((char) character);
}
} catch (IOException e) {
e.printStackTrace();
}
BufferedReader
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("example.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
FileWriter
try (FileWriter writer = new FileWriter("output.txt")) {
String data = "Hello, World!";
writer.write(data);
} catch (IOException e) {
e.printStackTrace();
}
BufferedWriter
try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
String line1 = "Line 1";
String line2 = "Line 2";
writer.write(line1);
writer.newLine(); // 写入换行符
writer.write(line2);
} catch (IOException e) {
e.printStackTrace();
}
需要注意的是,字符流底层仍然是通过字节流来进行数据传输的,字符流会在字节流的基础上进行字符编码和解码的处理。在处理文本数据时,字符流更为方便(推荐),因为它们可以自动处理字符集的转换。
根据实际需求和场景的不同,可以选择适合的I/O模型。BIO适用于连接数较小且简单的情况,NIO适用于需要管理大量连接的情况,AIO适用于需要实现高并发和高吞吐量的异步操作的情况。