这个并不是根本原因,面向过程也需要分配内存,计算内存偏移量,Java性能差的主要原因并不是因为它是面向对象语言,而是Java是半编译语言,最终的执行代码并不是可以直接被CPU执行的二进制机械码。
而面向过程语言大多都是直接编译成机械码在电脑上执行,并且其它一些面向过程的脚本语言性能也并不一定比Java好。
所谓跨平台性,是指java语言编写的程序,一次编译后,可以在多个系统平台上运行。
实现原理:Java程序是通过java虚拟机在系统平台上运行的,只要该系统可以安装相应的java虚拟机,该系统就可以运行java程序。
引用数据类型比较的是内存地址
)。它的作用也是判断两个对象是否相等。但它一般有两种使用情况:
public class test1 {
public static void main(String[] args) {
String a = new String("ab"); // a 为一个引用
String b = new String("ab"); // b为另一个引用,对象的内容一样
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
if (aa == bb) // true
System.out.println("aa==bb");
if (a == b) // false,非同一对象
System.out.println("a==b");
if (a.equals(b)) // true
System.out.println("aEQb");
if (42 == 42.0) { // true
System.out.println("true");
}
}
}
hashcode:hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!(可以快速找到所需要的对象)
我们先以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode: 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()
方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
通过我们可以看出:hashCode()
的作用就是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()
在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。
在 Java 中,JVM可以理解的代码就叫做
字节码
(即扩展名为.class
的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以 Java 程序运行时比较高效,而且,由于字节码并不针对一种特定的机器,因此,Java程序无须重新编译便可在多种不同操作系统的计算机上运行。
JDK是Java Development Kit,它是功能齐全的Java SDK。它拥有JRE所拥有的一切,还有编译器(javac)和工具(如javadoc和jdb)。它能够创建和编译程序。
JRE 是 Java运行时环境。它是运行已编译 Java 程序所需的所有内容的集合,包括 Java虚拟机(JVM),Java类库,java命令和其他的一些基础构件。但是,它不能用于创建新程序。
如果你只是为了运行一下 Java 程序的话,那么你只需要安装 JRE 就可以了。如果你需要进行一些 Java 编程方面的工作,那么你就需要安装JDK了。但是,这不是绝对的。有时,即使您不打算在计算机上进行任何Java开发,仍然需要安装JDK。例如,如果要使用JSP部署Web应用程序,那么从技术上讲,您只是在应用程序服务器中运行Java程序。那你为什么需要JDK呢?因为应用程序服务器会将 JSP 转换为 Java servlet,并且需要使用 JDK 来编译 servlet。
public class TypeConvert {
public static void main(String[] args) {
// 因为字面量 1 是 int 类型,它比 short 类型精度要高,因此不能隐式地将 int 类型下转型为 short 类型。
short s1 = 1;
// s1 = s1 + 1;
// 但是使用 += 运算符可以执行隐式类型转换。
s1 += 1;
// 上面的语句相当于将 s1 + 1 的计算结果进行了向下转型:
s1 = (short) (s1 + 1);
}
}
继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承我们能够非常方便地复用以前的代码。
关于继承如下 3 点请记住:
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
备注:在JDK8中,接口也可以定义静态方法,可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口,接口中定义了一样的默认方法,则必须重写,不然会报错。
static
修饰的,那么这个成员变量是属于类的,如果没有使用static
修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。总结一下 Java 中方法参数的使用情况:
private final char value[]
,所以 String 对象是不可变的。而StringBuilder 与 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组保存字符串char[]value
但是没有用 final 关键字修饰,所以这两种对象都是可变的。 public static void main(String[] args) {
// String
String str = "hello";
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
str += i; // 创建多少个对象,,
}
System.out.println("String: " + (System.currentTimeMillis() - start));
// StringBuffer
StringBuffer sb = new StringBuffer("hello");
long start1 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
sb.append(i);
}
System.out.println("StringBuffer: " + (System.currentTimeMillis() - start1));
// StringBuilder
StringBuilder stringBuilder = new StringBuilder("hello");
long start2 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
stringBuilder.append(i);
}
System.out.println("StringBuilder: " + (System.currentTimeMillis() - start2));
}
final关键字主要用在三个地方:变量、方法、类。
final修饰有啥好处
类名.静态变量名
类名.静态方法名()
import static
这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。this是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针。
this的用法在java中大体可以分为3种:
1.普通的直接引用,this相当于是指向当前对象本身。
2.形参与成员名字重名,用this来区分:
public Person(String name, int age) {
this.name = name;
this.age = age;
}
3.引用本类的构造函数
class Person{
private String name;
private int age;
public Person() {
}
public Person(String name) {
this.name = name;
}
public Person(String name, int age) {
this(name);
this.age = age;
}
}
super可以理解为是指向自己超(父)类对象的一个指针,而这个超类指的是离自己最近的一个父类。
super也有三种用法:
普通的直接引用与this类似,super相当于是指向当前对象的父类的引用,这样就可以用super.xxx来引用父类的成员。
子类中的成员变量或方法与父类中的成员变量或方法同名时,用super进行区分
class Person{
protected String name;
public Person(String name) {
this.name = name;
}
}
class Student extends Person{
private String name;
public Student(String name, String name1) {
super(name);
this.name = name1;
}
public void getInfo(){
System.out.println(this.name); //Child
System.out.println(super.name); //Father
}
}
public class Test {
public static void main(String[] args) {
Student s1 = new Student("Father","Child");
s1.getInfo();
}
}
3.引用父类构造函数
简单解释一下:
作用域 | 当前类 | 同package | 子孙类 | 其他package |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
friednly | √ | √ | × | × |
private | √ | × | × | × |
是程序无法处理的错误,表示运行应用程序中较严重问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时 JVM(Java 虚拟机)出现的问题。例如,Java虚拟机运行错误(Virtual MachineError),当 JVM 不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止。
这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应用时,如Java虚拟机运行错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在 Java中,错误通过Error的子类描述。
在以下4种特殊情况下,finally块不会被执行:
注意: 当try语句和finally语句中都有return语句时,在方法返回之前,finally语句的内容将被执行,并且finally语句的返回值将会覆盖原始的返回值。如下:
public static int f(int value) {
try {
return value * value;
} finally {
if (value == 2) {
return 0;
}
}
}
f(2)
,返回值将是0,因为finally语句的返回值覆盖了try语句块的返回值。Scanner input = **new** Scanner(System.in);
String s = input.nextLine();
input.close();
BufferedReader input = **new** BufferedReader(**new** InputStreamReader(System.in));
String s = input.readLine();
Java Io流共涉及40多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧密的联系, Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。
问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?
回答:字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
Socket
和 ServerSocket
相对应的 SocketChannel
和 ServerSocketChannel
两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发总结:一个进程可以包含多个线程,一个线程只能对应一个进程。一个程序至少有一个进程。进程是操作系统资源分配的基本单位,而线程是程序执行的最小单位
package com.lijie;
public class CreateThread extends Thread {
// run方法中编写 多线程需要执行的代码
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("i:" + i);
}
}
public static void main(String[] args) {
// 1.创建一个线程
CreateThread createThread = new CreateThread();
// 2.开始执行线程 注意 开启线程不是调用run方法,而是start方法
createThread.start();
}
}
package com.lijie;
public class CreateRunnable implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("i:" + i);
}
}
public static void main(String[] args) {
// 1.创建一个线程
CreateRunnable createThread = new CreateRunnable();
// 2.开始执行线程 注意 开启线程不是调用run方法,而是start方法
Thread thread = new Thread(createThread);
thread.start();
}
}
package com.lijie;
public class CreateRunnable {
public static void main(String[] args) {
//创建多线程创建开始
Thread thread = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("i:" + i);
}
}
});
thread.start();
}
}
一个新创建的线程并不会自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程。当start()方法创建线程运行的系统资源(这就叫就绪状态)
处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间片,只有获得CPU时间片才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。
时间片即CPU分配给各个程序的时间,每个线程被分配一个时间段,称作它的时间片。
Java提供了一种内置的锁机制来支持原子性:synchronized关键字
synchronized称为内置锁,当线程进入同步代码块之前自动获取到锁,代码块执行完成正常退出或代码块中抛出异常退出时会释放掉锁
即:线程A获取到锁后,线程B阻塞直到线程A释放锁,线程B才能获取到同一个锁
在我们平时的项目开发过程中,基本上很少会直接使用的反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模型也采用了反射机制,还有我们日常使用的Spring / Hibernate等框架也大量使用到了反射机制。
我们在使用JDBC连接数据库时使用Class.forName()
通过反射加载数据看的驱动程序;
Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring通过XML配置模式装载Bean的过程;
package com.lijie;
public class User {
private String userName;
private String alias;
public static void main(String[] args) {
try {
//第一种反射创建方式方式:
Class> forName = Class.forName("com.lijie.User"); //指定加载类
//第二种方式:java中每个类型都有class 属性
Class> forName2 = User.class;
//第三种方式:java语言中任何一个java对象都有getClass 方法
User user1 = new User();
Class> forName3 = user1.getClass();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//第一种反射创建方式:
Class<?> forName = Class.forName("com.lijie.User"); //指定加载类
//反射创建对象,赋值给对象
User user = (User)forName.newInstance();
//获取所有方法名称
Method[] methods = forName.getMethods();
for (Method method:methods) {
System.out.println("User类中的"+method.getName()+"方法");
}
//获取所有属性名称
Field[] fields = forName.getDeclaredFields();
for (Field field:fields) {
System.out.println("User类中的"+field.getName()+"属性");
}
package com.lijie;
public class User {
private String userName;
private String alias;
private User(){
}
}
package com.lijie;
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) {
try {
//私有构成参数创建对象:
Constructor<?> cs = User.class.getDeclaredConstructor();
cs.setAccessible(true);
User user=(User)cs.newInstance();
System.out.println(user);
} catch (Exception e) {
System.out.println(e);
}
}
}
package com.lijie;
public class User {
private String userName;
public String getUserName() {
return userName;
}
}
package com.lijie;
import java.lang.reflect.Field;
public class Test {
public static void main(String[] args) {
try {
// 为user对象私有属性赋值
Class<?> classUser = Class.forName("com.lijie.User");
// 获取到当前的所有属性
Field[] fields = classUser.getDeclaredFields();
for (Field field : fields) {
System.out.println(field.getName());
}
// 初始化对象
User user = (User) classUser.newInstance();
Field declaredField = classUser.getDeclaredField("userName");
// 标记为true 允许反射赋值
declaredField.setAccessible(true);
declaredField.set(user, "李杰");
System.out.println("使用反射机制给id赋值为:"+user.getUserName());
} catch (Exception e) {
System.out.println(e);
}
}
}