基本数据类型和引用数据类型的区别
-
基本数据类型
声明时直接在栈内存中开辟空间,并直接在当前内存中存放数据,赋值时传递的是变量中的值,总的来说,基本数据类型是传值的。 -
引用数据类型
会将实际的数据存放在堆内存中,同时,在栈内存中声明一个数组名或对象名,存放着在堆内存中的是地址; -
两者关系图
-
两者对比图
-
两者创建图
-
赋值运算符(=)的作用图
值传递和引用传递区别
-
值传递
在方法的调用过程中,实参把它的实际值传递给形参,此传递过程就是将实参的值复制一份传递到函数中,这样如果在函数中对该值(形参的值)进行了操作将不会影响实参的值。因为是直接复制,所以这种方式在传递大量数据时,运行效率会特别低下。 -
引用传递
引用传递弥补了值传递的不足,如果传递的数据量很大,直接复过去的话,会占用大量的内存空间,而引用传递就是将对象的地址值传递过去,函数接收的是原始值的首地址值。在方法的执行过程中,形参和实参的内容相同,指向同一块内存地址,也就是说操作的其实都是源数据,所以方法的执行将会影响到实际对象。(注意:这里所说的是改变源数据的内容,如:改变的对象的属性) -
实例
public class Example { String str = new String("hello"); char[] ch = {'a', 'b'}; public static void main(String[] args) { change(str, ch); System.out.println(str); //输出:hello System.out.println(ch); //输出:cd } public void change(String str, char[] ch) { str = "ok"; ch[0] = 'c'; //改变数据源内容 } }
-
过程分析图:
常用类:java.lang.Enum
-
使用场景
当我们需要限制一系列变量的时候,通常想到数组或者集合;其实很多时候我们需要限定变量需要做的事情很多,或者说如果被限制的变量可以做其他事情的话就更好了,而不是单纯的一个变量,那么,枚举的作用不仅仅可以让你使用限制在一个enum中的变量,并且这些变量的灵活性和拓展性很好。public enum WeekEnums { //注:枚举写在最前面,否则编译出错 Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday; private static String getWeek(WeekEnums weekEnums) { String week = null; switch (weekEnums) { case Sunday://星期天 week = "星期天"; //此处写逻辑处理代码 break; case Monday://星期一 week = "星期一"; //此处写逻辑处理代码 break; case Tuesday:// 星期二 week = "星期二"; //此处写逻辑处理代码 break; case Wednesday://星期三 week = "星期三"; //此处写逻辑处理代码 break; case Thursday:// 星期四 week = "星期四"; //此处写逻辑处理代码 break; case Friday://星期五 week = "星期五"; //此处写逻辑处理代码 break; case Saturday:// 星期六 week = "星期六"; //此处写逻辑处理代码 break; } return week; } } //获取方式: String weekday = WeekEnums.getWeek(WeekEnums.Friday);
常用类:java.math.BigDecimal
-
使用场景
float和double类型的主要设计目标是为了科学计算和工程计算。他们执行二进制浮点运算,这是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。但是,商业计算往往要求结果精确,这时候就应该使用BigDecimal。
常用类:java.lang.ThreadLocal
-
使用场景
在多线程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象。ThreadLocal是编程中空间换时间的体现。 -
ThreadLocal的内部结构
- 每个Thread线程内部都有一个Map。
- Map里面存储线程本地对象(key)和线程的变量副本(value)。
- 每个Thread线程内部都有一个Map。
- Thread对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
-
ThreadLocal的内部结构图
-
实例
//包含业务唯一标识的类 public class Context { private String transactionId; public String getTransactionId() { return transactionId; } public void setTransactionId(String transactionId) { this.transactionId = transactionId; } } // 其中引用了Context类 public class MyThreadLocal { private static final ThreadLocal<Context> userThreadLocal = new ThreadLocal<Context>(); public static void set(Context user){ userThreadLocal.set(user); } public static void unset(){ userThreadLocal.remove(); } public static Context get(){ return userThreadLocal.get(); } } //ThreadLocalDemo.java。生成并将业务标识设置到ThreadLocal中然后在业务方法中调用 public class ThreadLocalDemo implements Runnable{ private static AtomicInteger ai = new AtomicInteger(0); public void run() { Context context = new Context(); context.setTransactionId(getName()); MyThreadLocal.set(context); System.out.println("request["+Thread.currentThread().getName()+"]:"+context.getTransactionId()); new BusinessService().businessMethod(); MyThreadLocal.unset(); } private String getName() { return ai.getAndIncrement()+""; } public static void main(String[] args) { ThreadLocalDemo tld = new ThreadLocalDemo(); new Thread(tld).start(); new Thread(tld).start(); } } public class BusinessService { public void businessMethod() { Context context = MyThreadLocal.get(); System.out.println("service["+Thread.currentThread().getName()+"]:"+context.getTransactionId()); } }
String
- Q1:String s = new String("hollis");定义了几个对象。
A1:若常量池中已经存在”hollis”,则直接引用,也就是此时只会创建一个对象,如果常量池中不存在”hollis”,则先创建后引用,也就是有两个。 - Q2:如何理解String的intern方法?
A2:当一个String实例str调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用; -
实例
String s1 = "Hollis"; String s2 = new String("Hollis"); String s3 = new String("Hollis").intern(); System.out.println(s1 == s2); //输出:false System.out.println(s1 == s3); //输出:true
-
创建分析图
String s3 = new String("Hollis").intern();,在不调用intern情况,s3指向的是JVM在堆中创建的那个对象的引用的(如图中的s2)。但是当执行了intern方法时,s3将指向字符串常量池中的那个字符串常量。 -
String不变性
一旦一个string对象在内存(堆)中被创建出来,他就无法被修改。注意:String类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。如果你需要一个可修改的字符串,应该使用StringBuffer 或者 StringBuilder。否则会有大量时间浪费在垃圾回收上,因为每次试图修改都有新的string对象被创建出来。 -
实例
定义一个字符串String s = "abcd";
图解
使用变量来赋值变量String s2 = s;
图解
字符串连接s = s.concat("ef");
图解
-
为什么设计成不可变:
设计成不可变的主要目的是为了安全和高效 -
为什么数组有length属性?
首先,数组是一个容器对象,其中包含固定数量的同一类型的值。一旦数组被创建,他的长度就是固定的了。数组的长度可以作为final实例变量的长度。因此,长度可以被视为一个数组的属性。 -
为什么String有length()方法?
String背后的数据结构是一个char数组,所以没有必要来定义一个不必要的属性(因为该属性在char数值中已经提供了)。和C不同的是,Java中char的数组并不等于字符串,虽然String的内部机制是char数组实现的。(注:C语言中,并没有String类,定义字符串通常使用char string[6] = "hollis";的形式)。 -
equals()和hashcode()之间关系
在判断两个对象是否相等时,不要只使用equals方法判断。还要考虑其哈希码是否相等。尤其是和hashMap等与hash相关的数据结构一起使用时。
1、如果两个对象相等,那么他们一定有相同的哈希值(hash code)。
2、如果两个对象的哈希值相等,那么这两个对象有可能相等也有可能不相等。(需要再通过equals来判断)
关键字
-
四种访问控制区别图
-
泛型中K T V E ? object等的含义
E – Element (在集合中使用,因为集合中存放的是元素)
T – Type(Java 类)
K – Key(键)
V – Value(值)
N – Number(数值类型)
? – 表示不确定的java类型(无限制通配符类型)
Object – 是所有类的根类,任何类的对象都可以设置给该Object引用变量,使用的时候可能需要类型强制转换,但是用使用了泛型T、E等这些标识符后,在实际用之前类型就已经确定了,不需要再进行类型强制转换。 -
可变参数
它允许一个方法把任意数量的值作为参数。
可变参数的工作原理
可变参数在被使用的时候,他首先会创建一个数组,数组的长度就是调用该方法是传递的实参的个数,然后再把参数值全部放到这个数组当中,然后再把这个数组作为参数传递到被调用的方法中。
实例public static void main(String[] args) { print("a"); print("a", "b"); print("a", "b", "c"); } public static void print(String ... s){ for(String a: s) System.out.println(a); }
Comparable和Comparator
-
作用
用来做对象之间的比较的和对象排序。
Comparator实例class Dog { int size; Dog(int s) { size = s; } } class SizeComparator implements Comparator<Dog> { @Override public int compare(Dog d1, Dog d2) { return d1.size - d2.size; } } public class ImpComparable { public static void main(String[] args) { TreeSet<Dog> d = new TreeSet<Dog>(new SizeComparator()); // pass comparator d.add(new Dog(1)); d.add(new Dog(2)); d.add(new Dog(1)); } }
Comparable实例
class Dog implements Comparable<Dog>{ int size; Dog(int s) { size = s; } @Override public int compareTo(Dog o) { return o.size - this.size; } } public class ImpComparable { public static void main(String[] args) { TreeSet<Dog> d = new TreeSet<Dog>(); d.add(new Dog(1)); d.add(new Dog(2)); d.add(new Dog(1)); } }
重载(Overloading)
-
定义
简单说,就是函数或者方法有同样的名称,但是参数列表不相同的情形,这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。 -
实例
class Dog{ public void bark(){ System.out.println("woof "); } //overloading method public void bark(int num){ for(int i=0; i<num; i++) System.out.println("woof "); } }
重写(Overriding)
-
定义
重写指的是在Java的子类与父类中有两个名称、参数列表都相同的方法的情况。由于他们具有相同的方法签名,所以子类中的新方法将覆盖父类中原有的方法。 -
实例
class Dog{ public void bark(){ System.out.println("woof "); } } class Hound extends Dog{ public void sniff(){ System.out.println("sniff "); } public void bark(){ System.out.println("bowl"); } } public class OverridingTest{ public static void main(String [] args){ Dog dog = new Hound(); dog.bark(); } } //最终输出:bowl
组合和继承
-
组合定义
其实所谓的组合就是创建一个新类去调用已经创建并且调试好的类,那么这个新类就可以把它叫做是一个组合。例如:A类中有一个属性对象B,那么这个A类就是一个组合类。 -
继承定义
通过extends的关键字,它可以帮助我们继承,被继承的类我们称作父类,也可以叫做基类,超类都行,而继承者我们称作子类或者派生类等等。 -
两者比较
建议在同样可行的情况下,优先使用组合而不是继承。因为组合更安全,更简单,更灵活,更高效。继承要慎用,其使用场合仅限于你确信使用该技术有效的情况。一个判断方法是,问一问自己是否需要从新类向基类进行向上转型。如果是必须的,则继承是必要的。反之则应该好好考虑是否需要继承。
迭代与递归
-
实例
//Q1:n*(n-1)*(n-2) = ? //递归 int factorial (int n) { if (n == 1) { return 1; } else { return n*factorial(n-1); } } //迭代 int factorial (int n) { int product = 1; for(int i=2; i<n; i++) { product *= i; } return product; } //Q2:fib(n-1) + fib(n-2) = ? //递归 int fib (int n) { if (n == 0) { return 0; } else if (n == 1) { return 1; } else { return fib(n-1) + fib(n-2); } } //迭代 int fib (int n) { int fib = 0; int a = 1; for(int i=0; i<n; i++) { int temp = fib; fib = fib + a; a = temp; } return fib; }
-
两者比较图
从对比图建议能不用递归就不用递归,递归多数都可以用迭代来代替。
反射
-
使用场景
用于动态代理和动态创建类,获取对象所有属性和方法(包含私有默认父类)。java中所有对象都是Class这个类的实例对象;通过Class这个类对象就可以实现反射机制。 - 优点:反射机制就是增加程序的灵活性,避免将程序写死到代码里。如struts中请求的派发控制,当请求来到时,struts通过查询配置文件,找到该请求对应的action方法,然后通过反射实例化action,并调用响应method。
- 缺点:对性能有一定影响
-
获得Class的三种方式
1、通过 Object 类中的 getClass() 方法,知道对象时用Person p1 = new Person(); Class<?> c1 = p1.getClass();
2、直接通过 类名.class 的方式得到,该方法最为安全可靠,程序性能更高,说明任何一个类都有一个隐含的静态成员变量 class,
Class<?> c2 = Person.class;
3、通过 Class 对象的 forName() 静态方法来获取,用的最多,但可能抛出 ClassNotFoundException 异常,主要用于动态代理。
Class<?> c3 = Class.forName("com.ys.reflex.Person");
-
类加载图
上图可知:一个类在 JVM 中只会有一个 Class 实例,即我们对上面获取的 c1,c2,c3进行 equals 比较,发现都是true。 -
通过反射获取构造方法实例
package fanshe; public class Student { //---------------构造方法------------------- //(默认的构造方法) Student(String str){ System.out.println("(默认)的构造方法 s = " + str); } //无参构造方法 public Student(){ System.out.println("调用了公有、无参构造方法执行了。。。"); } //有一个参数的构造方法 public Student(char name){ System.out.println("姓名:" + name); } //有多个参数的构造方法 public Student(String name ,int age){ System.out.println("姓名:"+name+"年龄:"+ age);//这的执行效率有问题,以后解决。 } //受保护的构造方法 protected Student(boolean n){ System.out.println("受保护的构造方法 n = " + n); } //私有构造方法 private Student(int age){ System.out.println("私有的构造方法 年龄:"+ age); } } //测试类 package fanshe; import java.lang.reflect.Constructor; public class Constructors { public static void main(String[] args) throws Exception { //1.加载Class对象 Class clazz = Class.forName("fanshe.Student"); //2.获取所有公有构造方法 System.out.println("**********************所有公有构造方法*********************************"); Constructor[] conArray = clazz.getConstructors(); for(Constructor c : conArray){ System.out.println(c); } System.out.println("************所有的构造方法(包括:私有、受保护、默认、公有)***************"); conArray = clazz.getDeclaredConstructors(); for(Constructor c : conArray){ System.out.println(c); } System.out.println("*****************获取公有、无参的构造方法*******************************"); Constructor con = clazz.getConstructor(null); //1>、因为是无参的构造方法所以类型是一个null,不写也可以:这里需要的是一个参数的类型,切记是类型 //2>、返回的是描述这个无参构造函数的类对象。 System.out.println("con = " + con); //调用构造方法 Object obj = con.newInstance(); // System.out.println("obj = " + obj); // Student stu = (Student)obj; System.out.println("******************获取私有构造方法,并调用*******************************"); con = clazz.getDeclaredConstructor(char.class); System.out.println(con); //调用构造方法 con.setAccessible(true);//暴力访问(忽略掉访问修饰符) obj = con.newInstance('男'); } }
-
获取成员变量实例
package fanshe.field; public class Student { public Student(){ } //**********字段*************// public String name; protected int age; char sex; private String phoneNum; @Override public String toString() { return "Student [name=" + name + ", age=" + age + ", sex=" + sex + ", phoneNum=" + phoneNum + "]"; } } //测试类 package fanshe.field; import java.lang.reflect.Field; public class Fields { public static void main(String[] args) throws Exception { //1.获取Class对象 Class stuClass = Class.forName("fanshe.field.Student"); //2.获取字段 System.out.println("************获取所有公有的字段********************"); Field[] fieldArray = stuClass.getFields(); for(Field f : fieldArray){ System.out.println(f); } System.out.println("************获取所有的字段(包括私有、受保护、默认的)********************"); fieldArray = stuClass.getDeclaredFields(); for(Field f : fieldArray){ System.out.println(f); } System.out.println("*************获取公有字段**并调用***********************************"); Field f = stuClass.getField("name"); System.out.println(f); //获取一个对象 Object obj = stuClass.getConstructor().newInstance();//产生Student对象--》Student stu = new Student(); //为字段设置值 f.set(obj, "刘德华");//为Student对象中的name属性赋值--》stu.name = "刘德华" //验证 Student stu = (Student)obj; System.out.println("验证姓名:" + stu.name); System.out.println("**************获取私有字段****并调用********************************"); f = stuClass.getDeclaredField("phoneNum"); System.out.println(f); f.setAccessible(true);//暴力反射,解除私有限定 f.set(obj, "18888889999"); System.out.println("验证电话:" + stu); } }
-
获取成员方法实例
package fanshe.method; public class Student { //**************成员方法***************// public void show1(String s){ System.out.println("调用了:公有的,String参数的show1(): s = " + s); } protected void show2(){ System.out.println("调用了:受保护的,无参的show2()"); } void show3(){ System.out.println("调用了:默认的,无参的show3()"); } private String show4(int age){ System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age); return "abcd"; } } //测试类 package fanshe.method; import java.lang.reflect.Method; public class MethodClass { public static void main(String[] args) throws Exception { //1.获取Class对象 Class stuClass = Class.forName("fanshe.method.Student"); //2.获取所有公有方法 System.out.println("***************获取所有的”公有“方法*******************"); stuClass.getMethods(); Method[] methodArray = stuClass.getMethods(); for(Method m : methodArray){ System.out.println(m); } System.out.println("***************获取所有的方法,包括私有的*******************"); methodArray = stuClass.getDeclaredMethods(); for(Method m : methodArray){ System.out.println(m); } System.out.println("***************获取公有的show1()方法*******************"); Method m = stuClass.getMethod("show1", String.class); System.out.println(m); //实例化一个Student对象 Object obj = stuClass.getConstructor().newInstance(); m.invoke(obj, "刘德华"); System.out.println("***************获取私有的show4()方法******************"); m = stuClass.getDeclaredMethod("show4", int.class); System.out.println(m); m.setAccessible(true);//解除私有限定 Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参 System.out.println("返回值:" + result); } }
-
通过反射运行配置文件内容实例
public class Student { public void show(){ System.out.println("is show()"); } }
配置文件以txt文件为例子(pro.txt):
className = cn.fanshe.Student methodName = show
测试类:
import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.lang.reflect.Method; import java.util.Properties; public class Demo { public static void main(String[] args) throws Exception { //通过反射获取Class对象 Class stuClass = Class.forName(getValue("className"));//"cn.fanshe.Student" //2获取show()方法 Method m = stuClass.getMethod(getValue("methodName"));//show //3.调用show()方法 m.invoke(stuClass.getConstructor().newInstance()); } //此方法接收一个key,在配置文件中获取相应的value public static String getValue(String key) throws IOException{ Properties pro = new Properties();//获取配置文件的对象 FileReader in = new FileReader("pro.txt");//获取输入流 pro.load(in);//将流加载到配置文件对象中 in.close(); return pro.getProperty(key);//返回根据key获取的value值 } }
当我们升级这个系统时,不要Student类,而需要新写一个Student2的类时,这时只需要更改pro.txt的文件内容就可以了。代码就一点不用改动
public class Student2 { public void show2(){ System.out.println("is show2()"); } }
配置文件更改为:
className = cn.fanshe.Student2 methodName = show2
序列化和反序列化
- 序列化:对象序列化的最主要的用处就是在传递和保存对象的时候,保证对象的完整性和可传递性。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了Java对象的状态以及相关的描述信息。序列化机制的核心作用就是对象状态的保存与重建。
- 反序列化:客户端从文件中或网络上获得序列化后的对象字节流后,根据字节流中所保存的对象状态及描述信息,通过反序列化重建对象。
- 本质上讲,序列化就是把实体对象状态按照一定的格式写入到有序字节流,反序列化就是从有序字节流重建对象,恢复对象状态
-
使用场景
1、永久性保存对象,保存对象的字节序列到本地文件或者数据库中;
2、通过序列化以字节流的形式使对象在网络中进行传递和接收;
3、通过序列化在进程间传递对象; -
缺点
1、序列化后的码流太大
2、序列化性能太差 -
实现要求
只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。