Java中总共有八种基本的数据类型:byte(1)、short(2)、int(4)、long(8)、char(2)、float(4)、double(8)和boolean【PS:括号内的为所占的字节数】,每种基本类型都有其对应的包装类。
自动类型转换 :低级变量可以直接转换为高级变量,转换规则为:
byte→short(char)→int→long→float→double
例如,下面的语句可以编译通过:
byte b;
int i=b;
long l=b;
float f=b;
double d=b;
但是将double型变量赋值给float变量,不加强转的话会报错。
简单的说,装箱就是就是将基本数据类型转换为包装器类型;拆箱就是将包装器类型转换为基本数据类型。
例如:
Integer i = 10; //装箱
int n = i; //拆箱
注意事项:
对于Integer而言,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。例如:
public class Main {
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1==i2);
System.out.println(i3==i4);
}
}
输出的结果就为:
true
false
Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别。
当 **==**运算符的两个操作数都是包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程);另外,对于包装器类型,equals方法并不会进行类型转换。例如:
public class Main {
public static void main(String[] args) {
Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 320;
Integer f = 320;
Long g = 3L;
Long h = 2L;
// 因为c、d都是包装类型,所以返回true
System.out.println(c==d);
// 因为e、f都大于128,属于不同的对象,所以返回false
System.out.println(e==f);
// 因为含有算数运算符,所以比较的数值,返回true
System.out.println(c==(a+b));
// 因为equals不会类型转换,所以比较的是类型,返回true
System.out.println(c.equals(a+b));
// 比较数值,所以返回true
System.out.println(g==(a+b));
// 因为equals不会类型转换,a、b为Integer,所以返回false
System.out.println(g.equals(a+b));
// a向上转型为Long,所以返回true
System.out.println(g.equals(a+h));
}
}
Java修饰符:用来定义类、方法或者变量,通常放在语句的最前端。可分为:
Java中具有四种访问权限:
public : 对所有类可见。使用对象:类、接口、变量、方法
protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)。
default (即缺省,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
访问权限之间的对比如下表所示:
Java中提供的非访问修饰符有:
static:用来修饰类、方法和变量。
final:用来修饰类、方法和变量:
abstract:用来创建抽象类和抽象方法。
synchronized 和 volatile:主要用于线程的编程。
注意事项:
Java中的变量可分为三种类型:
它们三者的区别如下图所示:
Java面对对象具有三大特性:
重载:是在一个类里面,是多态在编译器的表现形式。判断方法:
重写:是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。是多态的运行期间的表现形式。判断条件:
注意事项:
抽象类(abstract)和接口(interface)的区别:
this关键字:是指向对象本身的一个指针。this只能在类中的非静态方法中使用,静态方法和静态的代码块中绝对不能出现this。
表示类中的属性和方法:函数参数或者参数中的局部变量和成员变量同名的情况下,成员变量被屏蔽,此时要访问成员变量则需要用"this.成员变量名"来访问成员变量。例如:
class B{
private int x = 1;
public void out(){
int x = 2;
System.out.print(x);//打印2
System.out.print(this.x);//打印1
}
}
表示当前对象:在函数中,需要引用该函数所属类的当前对象时候,直接使用this
class C{
public static void main(String[] args){
C c1 = new C();
c1.tell();
}
public static void tell(){
System.out.print(this);//打印当前对象的字符串表示
}
}
super关键字:
子类调用父类的构造方法:用super(参数列表)
的方式调用,参数不是必须的。同时,还要注意super(参数列表)
这条语句只能在子类构造方法中的第一行。例如:
class A{
public A(){
System.out.print("A");
}
}
class B extends A{
public B(){
super();//调用父类构造方法,打印A
System.out.print("B");
}
}
访问父类中被覆盖的同名变量或者方法:如果子类覆盖了父类的中某个方法的实现,可以通过使用 super 关键字来引用父类的方法实现。例如:
class A{
public int a = 1;//可以直接赋值,不用通过构造函数
public void say(){
System.out.print(a);
}
}
class B extends A{
private int a = 2;
public void say(){
System.out.print(super.a);//访问父类的a变量,前提是父类的a变量是公有的
}
public void tell(){
super.say();//调用父类的say()方法而不是子类的say()方法
}
}
在Java中,构造方法的主要作用是完后才能对象的初始化工作,把定义对象的参数传给对象的域。
注意事项:
构造方法的方法名必须与类名相同;
构造方法没有返回类型,也不能定义为void,在方法名前不声明方法类型;
一个类可以定义多个构造方法,如果类在定义时没有定义构造方法,编译器会自动插入一个无参数的默认构造器;
子类不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
Java把异常当作对象来处理,并定义一个基类java.lang.Throwable
作为所有异常的超类。
throwable又可以分为两类:
Exception又可分为:
Java的异常处理机制可分为:
对于异常捕获,我们需要注意的是一下几点:
try语句在返回前,将其他所有的操作执行完,保留好要返回的值,而后转入执行finally中的语句,而后分为以下三种情况:
情况一:如果finally中有return语句,则会将try中的return语句”覆盖“掉,直接执行finally中的return语句,得到返回值,这样便无法得到try之前保留好的返回值。
情况二:如果finally中没有return语句,也没有改变要返回值,则执行完finally中的语句后,会接着执行try中的return语句,返回之前保留的值。
情况三:如果finally中没有return语句,但是改变了要返回的值,这里有点类似与引用传递和值传递的区别,分以下两种情况:
如果return的数据是基本数据类型或文本字符串,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块之前保留的值。
如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值
对于异常的抛出,需要注意的是:throws和throw的区别,
throw:
throws:
Java中的Object类是所有类的父类,它提供了以下11个方法:
public final native Class getClass()
:返回当前运行时对象的Class对象,getClass方法是一个final方法,不允许子类重写,并且也是一个native方法。
public native int hashCode()
: 返回散列值,而 equals() 是用来判断两个实例是否等价。等价的两个实例散列值一定要相同,但是散列值相同的两个实例不一定等价。
public boolean equals(Object obj)
:在非空对象引用上equlas具有以下特性:
(一)自反性
x.equals(x); // true
(二)对称性
x.equals(y) == y.equals(x) // true
(三)传递性
if(x.equals(y) && y.equals(z)) {
x.equals(z); // true;
}
(四)一致性:多次调用 equals() 方法结果不变
x.equals(y) == x.equals(y); // true
(五)与 null 的比较:对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false
x.euqals(null); // false;
protected native Object clone() throws CloneNotSupportedException
:是一个protected的native方法。由于Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会抛出异常。且clone函数具有以下特性:
public String toString()
:Object对象的默认实现,即输出类的名字@实例的哈希码的16进制
public final native void notify()
:是一个native方法,并且也是final的,不允许子类重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果所有的线程都在此对象上等待,那么只会选择一个线程。选择是任意性的,并在对实现做出决定时发生。
public final native void notifyAll()
:跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
public final native void wait(long timeout) throws InterruptedException
:是一个native方法,并且也是final的,不允许子类重写。wait方法会让当前线程等待直到另外一个线程调用对象的notify或notifyAll方法,或者超过参数设置的timeout超时时间。
public final void wait(long timeout, int nanos) throws InterruptedException
:跟wait(long timeout)方法类似,多了一个nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)
public final void wait() throws InterruptedException
:跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间。
protected void finalize() throws Throwable { }
:该方法的作用是实例被垃圾回收器回收的时候触发的操作finalize方法是一个protected方法,Object类的默认实现是不进行任何操作。
==
:如果比较的对象是基本数据类型,则比较的是数值是否相等;如果比较的是引用数据类型,则比较的是对象的地址值是否相等。在不遇到算术运算的情况下,不会自动拆箱。
equals()
:用来比较方法两个对象的内容是否相等。
注意:equals 方法不能用于基本数据类型的变量,如果没有对 equals 方法进行重写,则比较的是引用类型的变量所指向的对象的地址。
因为考虑到类似HashMap、HashTable、HashSet的这种散列的数据类型的运用。在Object类中,hashCode方法是通过Object对象的地址计算出来的,因为Object对象只与自身相等,所以同一个对象的地址总是相等的,计算取得的哈希码也必然相等,对于不同的对象,由于地址不同,所获取的哈希码自然也不会相等。所以如果重写了equals方法,而不重写hashcode方法,就可能会造成两个相同对象的重复插入。
notify()方法不能唤醒某个具体的线程,所以只有一个线程等待的时候才有用。而notifyAll()唤醒所有的线程,并允许他们争夺锁,确保只有一个线程能继续运行
Java提供的锁是对象级的而不是线程级。每个对象都有锁,通过线程获得。因为它们属于锁的操作,而锁属于对象。
new一个对象: 先根据new后面的类型去分配内存,然后再调用构造函数填充对象的各个域,初始化完成后,一个对象创建完毕,可以把他的引用(地址)发布到外部。
clone一个对象:首先分配与原对象同样大小的内存空间,然后根据原对象中对应的各个域,填充新对象的域,填充完成之后,clone 方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部
浅拷贝:拷贝实例和原始实例的引用类型引用同一个对象;
深拷贝:拷贝实例和原始实例的引用类型引用不同对象。
原理图大概如图所示:
Java 中的反射首先是能够获取到Java中要反射类的字节码 ,获取字节码有三种方法 :
然后将字节码中的方法,变量,构造函数等映射成相应的 Method、Filed、Constructor 等类,这些类提供了丰富的方法可以被我们所使用。
Java反射机制主要提供一下的功能:
静态代理:通常只代理一个类,事先知道要代理的是什么。
动态代理:动态代理是代理一个接口下的多个实现类,不知道要代理什么东西,只有在运行时才知道。动态代理是实现 JDK 里的 InvocationHandler 接口的 invoke 方法,但注意的是代理的是接口,也就是你的业务类必须要实现接口,通过 Proxy 里的 newProxyInstance 得到代理对象。
例如:下图为实现一个ArrayList的动态代理类:
public class proxy {
public static void main(String[] args) {
final ArrayList<String> list = new ArrayList<>();
ClassLoader classLoader = list.getClass().getClassLoader();
Class<?>[] interfaces = list.getClass().getInterfaces();
List<String> listProxy=(List<String>) Proxy.newProxyInstance(classLoader,interfaces , new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(list,args);
}
});
listProxy.add("你好") ;
System.out.println(list);
}
}
还有一种动态代理 CGLIB,代理的是类,不需要业务类继承接口,通过派生的子类来实现代理。通过在运行
时,动态修改字节码达到修改类的目的。
Java 注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。
其中Java有四种元注解,用来标注其他的注解:
出于规范的目的,Java建议使用单个大写字母来代表类型参数。常见的如:
泛型方法****
public static < E > void printArray( E[] inputArray ){
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
}
泛型类****
public class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
通配符具有下面三种形式:
:被称为无限定通配符,它其中的 ? 其实代表的是未知类型,所以涉及到 ? 时的操作,一定与具体类型无关。
:被称作有上限的通配符,表示该通配符所代表的类型是 T 类型的子类。
:被称作有下限的通配符,表示该通配符所代表的类型是 T 类型的父类。Java 中的泛型基本上都是在编译器这个层次来实现的。在生成的 Java 字节代码中是不包含泛型中的类型信息的。类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是 Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。
注意事项:
内部类可分为:成员内部类(类中)、局部内部类(方法中)、匿名内部类、和静态内部类
定义在类内部的非静态类,就是成员内部类:它具有以下几个特点:
例如:
public class Out {
private static int a;
private int b;
public class Inner {
public void print() {
System.out.println(a);
System.out.println(b);
}
}
}
嵌套在方法和作用域中,不能使用public
、private
、protect
。
public class Out {
private static int a;
private int b;
public void test(final int c) {
final int d = 1;
class Inner {
public void print() {
System.out.println(c);
}
}
}
}
匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。
具有以下的特性:
例如:
abstract class Person {
public abstract void eat();
}
public class Demo {
public static void main(String[] args) {
Person p = new Person() {
public void eat() {
System.out.println("eat something");
}
};
p.eat();
}
}
定义在类内部的静态类,就是静态内部类。具有以下特点:
例如:
public class Out {
private static int a;
private int b;
public static class Inner {
public void print() {
System.out.println(a);
}
}
}
Enum 是jdk1.5以后引入的一个新特性。这个特性可以使得我们的代码变得更加的简洁、安全以及便捷。
常量的使用:以前我们定义常量会选择这样的方式:public static final,而当枚举出现后,我们可以使用这样的方式实现常量的定义:
public enum Day {
MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
自定义方法:使用Enum实现对异常的统一定义:
public enum ResultEnum {
/**
* 系统未知错误
*/
UNKONW_ERROR(-1,"未知错误:"),
/**
* 请求成功
*/
SUCCESS(0,"success"),
/**
* 请求缺少参数
*/
LACK_PARAMETER(1,"缺少参数");
/**
* 错误码
*/
private Integer errorCode;
/**
* 错误提示信息
*/
private String message;
ResultEnum(Integer errorCode, String message) {
this.errorCode = errorCode;
this.message = message;
}
public Integer getErrorCode() {
return errorCode;
}
public String getMessage() {
return message;
}
}
switch使用:JDK1.6之前的switch语句只支持int,char,enum类型,使用枚举,能让我们的代码可读性更强。(jdk1.7以后Switch补充支持了string类型)
enum Color {GREEN,RED,BLUE}
public class EnumDemo4 {
public static void printName(Color color){
switch (color){
case BLUE: //无需使用Color进行引用
System.out.println("蓝色");
break;
case RED:
System.out.println("红色");
break;
case GREEN:
System.out.println("绿色");
break;
}
}
public static void main(String[] args){
printName(Color.BLUE);
printName(Color.RED);
printName(Color.GREEN);
}
}
实际上在使用关键字enum创建枚举类型并编译后,编译器会为我们生成一个相关的class,这个类继承了Java API中的java.lang.Enum类,也就是说通过关键字enum创建枚举类型在编译后事实上也是一个class类型而且该类继承自java.lang.Enum类。