Java基础知识
1、在Java语言中多态性体现在由方法重载实现的静态多态性和方法重写实现的动态多态性。
2、final 类不能被继承,即不能有子类。
3、abstract 类不能创建对象,必须产生其子类,由子类创建对象。
4、String 类是不可变类,对象所包含的字符串内容永远不会被改变。
5、如果一个方法被修饰为 final 方法,则这个方法不能被重写。
6、对于 abstract 方法,只允许声明,而不允许实现。
7、如果一个方法被修饰为final方法,则这个方法不能被重写 。
8、如果一个类是一个abstract类的子类,它必须具体实现父类 的abstract方法。
9、局部变量的名字与成员变量的名字相同,若想在该方法内使用成员变量,必须使用关键字 this 。
10、在关键字中能代表当前类或对象本身的是 this 。
11、如果在子类中想使用被子类隐藏的父类的成员变量或方法可以使用关键字 super 。
Java中的类修饰符
之前每次写小测试程序的时候,总是把一个类放在一个Java文件中,按理说这样比较规范,可主要原因是我是在不知道怎么在一个文件里放多个类,可见java基础有多差了。。。只要把类的属性改成默认的就可以了,也就是前面什么控制符都不加,采用默认的方式。
Java语言程序设计中,定义类时除了要使用class 关键字说明所定义的数据类型是类,还可以在 class 之前增加若干类的修饰符来限定所定义类的操作特性,说明类的属性。
类的修饰符分为:可访问控制符和非访问控制符两种。
可访问控制符是: 公共类修饰符 public
非访问控制符有:抽象类修饰符 abstract
最终类修饰符:final
不加修饰符的时候 即直接声明 class A{ }
在这种情况下,class前面没有加任何的访问修饰符,通常称为“默认访问模式”,在该模式下,这个类只能被同一个包中的类访问或引用,这一访问特性又称包访问性。
公共类的修饰符 public
一个java的源文件中,规定最多只能有一个public 类,当然也可以一个都没有。如果有public公共类,则规定文件名只能public 公共类的类名称一致,若是没有,则文件名可以任意。作为一个java程序的入口(javaSE)的类必须为public类。
所谓公共类是指这个类可以被所有的其它类或其它包中的类访问和引用,也就是说这个类作为一个整体,是可见的、可以使用的,程序的其它部分可以创建这个类的对象、访问这个类内部公共的 (用可访问控制符 public 定义的 )变量和方法。
哪些类应定义为公共类呢?
(1)个Java程序的主类都必须定义为公共类,用 public 修饰;
(2)作为公共工具供其它类和程序使用的类应定义为公共类,用 public 修饰。
抽象类修饰符 abstract
凡是用修饰符 abstract修饰的类,被称为抽象类。接口是一个100%的纯抽象类。
最终类修饰符 final
当一个类不可能有子类时(final类是不可以被继承的)可以用修饰符 final把它说明为最终类。
被定义为 final类通常是一些有固定作用、用来完成某种标准功能的类。如Java系统定义好的用来实现网络功能的InetAddress、Socket等类都是 final类。
如果把有继承关系的类用树表示出来,不难看到树的叶结点应该被定义为final最终类。
将一个类定义为 final 类。就可以把它的属性和功能固定下来,与它的类名形成稳定的映射关系,从而保证引用这个类时所实现的功能正确无误。
注意:修饰符abstract和修饰符final 不能同时修饰同一个类,因为abstract类是没有具体对象的类,它必须有子类,即就是是用来被继承的;而 final类是不可能有子类的类,所以用abstract和final修饰同一个类是无意义的。
Java中5种创建对象的方式
下面给出它们的例子还有它们的字节码
使用new关键字 | } → 调用了构造函数 |
使用Class类的newInstance方法 | } → 调用了构造函数 |
使用Constructor类的newInstance方法 | } → 调用了构造函数 |
使用clone方法 | } → 没有调用构造函数 |
使用反序列化 | } → 没有调用构造函数 |
如果你运行了末尾的的程序,你会发现方法1,2,3用构造函数创建对象,方法4,5没有调用构造函数。
1.使用new关键字
这是最常见也是最简单的创建对象的方式了。通过这种方式,我们可以调用任意的构造函数(无参的和带参数的)。
Employee emp1 = new Employee();
0: new #19 // class org/programming/mitra/exercises/Employee
3: dup
4: invokespecial #21 // Method org/programming/mitra/exercises/Employee."":()V
2.使用Class类的newInstance方法
我们也可以使用Class类的newInstance方法创建对象。这个newInstance方法调用无参的构造函数创建对象。
我们可以通过下面方式调用newInstance方法创建对象:
Employee emp2 = (Employee) Class.forName("org.programming.mitra.exercises.Employee").newInstance();
或者
Employee emp2 = Employee.class.newInstance();
51: invokevirtual #70 // Method java/lang/Class.newInstance:()Ljava/lang/Object;
3.使用Constructor类的newInstance方法
和Class类的newInstance方法很像, java.lang.reflect.Constructor类里也有一个newInstance方法可以创建对象。我们可以通过这个newInstance方法调用有参数的和私有的构造函数。
Constructor<Employee> constructor = Employee.class.getConstructor();
Employee emp3 = constructor.newInstance();
111: invokevirtual #80 // Method java/lang/reflect/Constructor.newInstance:([Ljava/lang/Object;)Ljava/lang/Object;
这两种newInstance方法就是大家所说的反射。事实上Class的newInstance方法内部调用Constructor的newInstance方法。这也是众多框架,如Spring、Hibernate、Struts等使用后者的原因。想了解这两个newInstance方法的区别,请看这篇Creating objects through Reflection in Java with Example.
4.使用clone方法
无论何时我们调用一个对象的clone方法,jvm就会创建一个新的对象,将前面对象的内容全部拷贝进去。用clone方法创建对象并不会调用任何构造函数。
要使用clone方法,我们需要先实现Cloneable接口并实现其定义的clone方法。
Employee emp4 = (Employee) emp3.clone();
162: invokevirtual #87 // Method org/programming/mitra/exercises/Employee.clone ()Ljava/lang/Object;
5.使用反序列化
当我们序列化和反序列化一个对象,jvm会给我们创建一个单独的对象。在反序列化时,jvm创建对象并不会调用任何构造函数。
为了反序列化一个对象,我们需要让我们的类实现Serializable接口
ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));
Employee emp5 = (Employee) in.readObject();
261: invokevirtual #118 // Method java/io/ObjectInputStream.readObject:()Ljava/lang/Object;
我们从上面的字节码片段可以看到,除了第1个方法,其他4个方法全都转变为invokevirtual(创建对象的直接方法),第一个方法转变为两个调用,new和invokespecial(构造函数调用)。
例子
让我们看一看为下面这个Employee类创建对象:
class Employee implements Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private String name;
public Employee() {
System.out.println("Employee Constructor Called...");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Employee other = (Employee) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
@Override
public String toString() {
return "Employee [name=" + name + "]";
}
@Override
public Object clone() {
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
}
下面的Java程序中,我们将用5种方式创建Employee对象。你可以从GitHub找到这些代码。
public class ObjectCreation {
public static void main(String... args) throws Exception {
// By using new keyword
Employee emp1 = new Employee();
emp1.setName("Naresh");
System.out.println(emp1 + ", hashcode : " + emp1.hashCode());
// By using Class class's newInstance() method
Employee emp2 = (Employee) Class.forName("org.programming.mitra.exercises.Employee")
.newInstance();
// Or we can simply do this
// Employee emp2 = Employee.class.newInstance();
emp2.setName("Rishi");
System.out.println(emp2 + ", hashcode : " + emp2.hashCode());
// By using Constructor class's newInstance() method
Constructor<Employee> constructor = Employee.class.getConstructor();
Employee emp3 = constructor.newInstance();
emp3.setName("Yogesh");
System.out.println(emp3 + ", hashcode : " + emp3.hashCode());
// By using clone() method
Employee emp4 = (Employee) emp3.clone();
emp4.setName("Atul");
System.out.println(emp4 + ", hashcode : " + emp4.hashCode());
// By using Deserialization
// Serialization
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data.obj"));
out.writeObject(emp4);
out.close();
//Deserialization
ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));
Employee emp5 = (Employee) in.readObject();
in.close();
emp5.setName("Akash");
System.out.println(emp5 + ", hashcode : " + emp5.hashCode());
}
}
程序会输出:
Employee Constructor Called...
Employee [name=Naresh], hashcode : -1968815046
Employee Constructor Called...
Employee [name=Rishi], hashcode : 78970652
Employee Constructor Called...
Employee [name=Yogesh], hashcode : -1641292792
Employee [name=Atul], hashcode : 2051657
Employee [name=Akash], hashcode : 63313419
java实现三个线程A B C,A线程打印A之后才能B线程打印B,然后接着C线程执行打印C,继而循环下去
在JAVA中,是没有类似于PV操作、进程互斥等相关的方法的。JAVA的进程同步是通过synchronized()来实现的,需要说明的是,JAVA的synchronized()方法类似于操作系统概念中的互斥内存块,在JAVA中的Object类型中,都是带有一个内存锁的,在有线程获取该内存锁后,其它线程无法访问该内存,从而实现JAVA中简单的同步、互斥操作。明白这个原理,就能理解为什么synchronized(this)与synchronized(static XXX)的区别了,synchronized就是针对内存区块申请内存锁,this关键字代表类的一个对象,所以其内存锁是针对相同对象的互斥操作,而static成员属于类专有,其内存空间为该类所有成员共有,这就导致synchronized()对static成员加锁,相当于对类加锁,也就是在该类的所有成员间实现互斥,在同一时间只有一个线程可访问该类的实例。如果只是简单的想要实现在JAVA中的线程互斥,明白这些基本就已经够了。但如果需要在线程间相互唤醒的话就需要借助Object.wait(), Object.nofity()了。
Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。
单单在概念上理解清楚了还不够,需要在实际的例子中进行测试才能更好的理解。对Object.wait(),Object.notify()的应用最经典的例子,应该是三线程打印ABC的问题了吧,这是一道比较经典的面试题,题目要求如下:
建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。代码如下:
[java] view plaincopy
- public class MyThreadPrinter2 implements Runnable {
- private String name;
- private Object prev;
- private Object self;
- private MyThreadPrinter2(String name, Object prev, Object self) {
- this.name = name;
- this.prev = prev;
- this.self = self;
- }
- @Override
- public void run() {
- int count = 10;
- while (count > 0) {
- synchronized (prev) {
- synchronized (self) {
- System.out.print(name);
- count--;
- self.notify();
- }
- try {
- prev.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- public static void main(String[] args) throws Exception {
- Object a = new Object();
- Object b = new Object();
- Object c = new Object();
- MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);
- MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);
- MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);
- new Thread(pa).start();
- new Thread(pb).start();
- new Thread(pc).start(); }
- }
先来解释一下其整体思路,从大的方向上来讲,该问题为三线程间的同步唤醒操作,主要的目的就是ThreadA->ThreadB->ThreadC->ThreadA循环执行三个线程。为了控制线程执行的顺序,那么就必须要确定唤醒、等待的顺序,所以每一个线程必须同时持有两个对象锁,才能继续执行。一个对象锁是prev,就是前一个线程所持有的对象锁。还有一个就是自身对象锁。主要的思想就是,为了控制执行的顺序,必须要先持有prev锁,也就前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备时打印,之后首先调用self.notify()释放自身对象锁,唤醒下一个等待线程,再调用prev.wait()释放prev对象锁,终止当前线程,等待循环结束后再次被唤醒。运行上述代码,可以发现三个线程循环打印ABC,共10次。程序运行的主要过程就是A线程最先运行,持有C,A对象锁,后释放A,C锁,唤醒B。线程B等待A锁,再申请B锁,后打印B,再释放B,A锁,唤醒C,线程C等待B锁,再申请C锁,后打印C,再释放C,B锁,唤醒A。看起来似乎没什么问题,但如果你仔细想一下,就会发现有问题,就是初始条件,三个线程按照A,B,C的顺序来启动,按照前面的思考,A唤醒B,B唤醒C,C再唤醒A。但是这种假设依赖于JVM中线程调度、执行的顺序。具体来说就是,在main主线程启动ThreadA后,需要在ThreadA执行完,在prev.wait()等待时,再切回线程启动ThreadB,ThreadB执行完,在prev.wait()等待时,再切回主线程,启动ThreadC,只有JVM按照这个线程运行顺序执行,才能保证输出的结果是正确的。而这依赖于JVM的具体实现。考虑一种情况,如下:如果主线程在启动A后,执行A,过程中又切回主线程,启动了ThreadB,ThreadC,之后,由于A线程尚未释放self.notify,也就是B需要在synchronized(prev)处等待,而这时C却调用synchronized(prev)获取了对b的对象锁。这样,在A调用完后,同时ThreadB获取了prev也就是a的对象锁,ThreadC的执行条件就已经满足了,会打印C,之后释放c,及b的对象锁,这时ThreadB具备了运行条件,会打印B,也就是循环变成了ACBACB了。这种情况,可以通过在run中主动释放CPU,来进行模拟。代码如下:
[java] view plaincopy
- public void run() {
- int count = 10;
- while (count > 0) {
- synchronized (prev) {
- synchronized (self) {
- System.out.print(name);
- count--;
- try{
- Thread.sleep(1);
- }
- catch (InterruptedException e){
- e.printStackTrace();
- }
- self.notify();
- }
- try {
- prev.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
运行后的打印结果就变成了ACBACB了。为了避免这种与JVM调度有关的不确定性。需要让A,B,C三个线程以确定的顺序启动,最终代码如下:
[java] view plaincopy
- public class MyThreadPrinter2 implements Runnable {
- private String name;
- private Object prev;
- private Object self;
- private MyThreadPrinter2(String name, Object prev, Object self) {
- this.name = name;
- this.prev = prev;
- this.self = self;
- }
- @Override
- public void run() {
- int count = 10;
- while (count > 0) {
- synchronized (prev) {
- synchronized (self) {
- System.out.print(name);
- count--;
- try{
- Thread.sleep(1);
- }
- catch (InterruptedException e){
- e.printStackTrace();
- }
- self.notify();
- }
- try {
- prev.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- public static void main(String[] args) throws Exception {
- Object a = new Object();
- Object b = new Object();
- Object c = new Object();
- MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);
- MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);
- MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);
- new Thread(pa).start();
- Thread.sleep(10);
- new Thread(pb).start();
- Thread.sleep(10);
- new Thread(pc).start();
- Thread.sleep(10);
- }
- }
文章来自:http://blog.csdn.net/u012110719/article/details/47161789
这样才可以完美的解决该问题。通过这个例子也是想说明一下,很多理论、概念如Obj.wait(),Obj.notify()等,理解起来,比较简单,但是在实际的应用当中,这里却是往往出现问题的地方。需要更加深入的理解。并在解决问题的过程中不断加深对概念的掌握。