为毛要设计成把main放到类里边 Java 的两大数据类型:内置数据类型 引用数据类型 。八种基本类型:六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。 protected 需要从以下两个点来分析说明:子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问; 子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的protected方法。 final 修饰符 abstract 修饰符 synchronized 修饰符 transient 修饰符 volatile 修饰符 在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。 当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。 使用 dataType[] arrayRefVar 的声明风格声明数组变量。Java5 引入了一种主要用于数组的增强型 For-Each 循环。 java.util.regex 包是 Java 标准库中用于支持正则表达式操作的包。Pattern 类 Matcher 类 。 重载的方法必须拥有不同的参数列表。你不能仅仅依据修饰符或者返回类型的不同来重载方法。 JDK 1.5 开始,Java支持传递同类型的可变参数给一个方法。 typeName... parameterName 在方法声明中,在指定参数类型后加一个省略号(...) 。一个方法中只能指定一个可变参数,它必须是方法的最后一个参数。任何普通的参数必须在它之前声明。 finalize() 方法 BufferedReader。控制台的输出由 print( ) 和 println() 完成,这些方法都由类 PrintStream 定义,System.out 是该类对象的一个引用。 File FileInputStream FileOutputStream OutputStreamWriter InputStreamReader java.util.Scanner 是 Java5 的新特征,我们可以通过 Scanner 类来获取用户的输入。 Exception类:是所有异常类的父类,它提供了一些方法来获取异常信息,如 getMessage()、printStackTrace() 等。 finally:用于包含无论是否发生异常都需要执行的代码块。 try/catch代码块中的代码称为保护代码,Catch 语句包含要捕获异常类型的声明。当保护代码块中发生一个异常时,try 后面的 catch 块就会被检查。try 代码后不能既没 catch 块也没 finally 块。 throw 关键字用于在代码中抛出异常,而 throws 关键字用于在方法声明中指定可能会抛出的异常类型。 java知道会有异常为什么不直接处理了要抛出异常???感觉还是懒得写错误处理代码,谁调用谁写处理代码。 try-with-resources 是一种异常处理机制,它能够自动关闭在 try 块中声明的资源,无需显式地在 finally 块中关闭。 JVM异常 程序级异常 继承可以使用 extends 和 implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承 Object(这个类在 java.lang 包中,所以不需要 import)祖先类。 在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。 另一个角度来说子类其实继承了超类的一切,而且用构造器创建子类的对象前,会先调用父类构造器,例子如后图所示,子类的对象是包括了子类所不能从父类中继承的私有成员的,它能获取父类中private权限的属性或方法,只不过看不到private修饰的内容而已,但可以通过调用父类本身的方法来获取和利用。 super this 关键字 子类可以通过super关键字来访问父类的成员(包括私有成员),这确实看起来违反了封装性的原则。但是父类的私有成员变量虽然可以被子类读取但不能直接修改。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。 重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。 虚函数的存在是为了多态。Java 中没有虚函数的概念,它的普通函数就相当于 C++ 的虚函数,动态绑定是Java的默认行为。如果 Java 中不希望某个函数具有虚函数特性,可以加上 final 关键字变成非虚函数。 多态存在的三个必要条件:继承 重写 父类引用指向子类对象Parent p = new Child(); Abstract 关键字同样可以用来声明抽象方法,抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。如果一个类包含抽象方法,那么该类必须是抽象类。 类使用implements关键字实现接口,类要实现接口中所有的方法。接口支持多继承,接口中的方法会被隐式的指定为 public abstract,接口中的变量会被隐式的指定为 public static final。JDK 1.8 以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰。JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。一个接口能继承另一个接口,接口的继承使用extends关键字。在Java中,类的多继承是不合法,但接口允许多继承。public interface Hockey extends Sports, Event 标记接口 Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。 如果一个源文件包含了这个包提供的类、接口、枚举或者注释类型的时候,都必须将这个包的声明放在这个源文件的开头。通常使用小写的字母来命名避免与类、接口名字的冲突。 如果一个源文件中没有使用包声明,那么其中的类,函数,枚举,注释等将被放在一个无名的包(unnamed package)中。 在 java 源文件中 import 语句必须位于 Java 源文件的头部,位于 package 语句之后。注意,使用通配符 * 导入整个包时,只会导入包中的类,而不会导入包中的子包。包名必须与相应的字节码所在的目录结构相吻合。一个公司使用它互联网域名的颠倒形式来作为它的包名。 编译之后的 .class 文件和 .java 源文件一样,它们放置的目录跟包的名字对应起来。但是,并不要求 .class 文件的路径跟相应的 .java 的路径一样。你可以分开来安排源码和类的目录。 \sources\com\runoob\test\Runoob.java \classes\com\runoob\test\Google.class,你可以将你的类目录分享给其他的编程人员,而不用透露自己的源码。 类目录的绝对路径叫做 class path。设置在系统变量 CLASSPATH 中。编译器和 java 虚拟机通过将 package 名字加到 class path 后来构造 .class 文件的路径。默认情况下,编译器和 JVM 查找当前目录。JAR 文件按包含 Java 平台相关的类,所以他们的目录默认放在了 class path 中。 Java 反射(Reflection)。反射提供了一种动态地操作类的能力,这在很多框架和库中被广泛使用,例如Spring框架的依赖注入。 每个类在 JVM 中都有一个与之相关的 Class 对象。 数组(Arrays)列表(Lists) 集合(Sets) 映射(Maps)栈(Stack) 队列(Queue)堆(Heap)树(Trees)图(Graphs) Java 集合框架提供了一套性能优良,使用方便的接口和类,java集合框架位于java.util包中 ArrayList LinkedList HashSet HashMap HashSet 是一个不允许有重复元素的集合。 HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。 Java迭代器(Iterator)是 Java 集合框架中的一种机制,是一种用于遍历集合(如列表、集合和映射等)的接口。Iterator 是 Java 迭代器最简单的实现 。通过使用迭代器,我们可以逐个访问集合中的元素,而不需要使用传统的 索引。使用迭代器遍历集合时,如果在遍历过程中对集合进行了修改(例如添加或删除元素),可能会导致 ConcurrentModificationException 异常,为了避免这个问题,可以使用迭代器自身的 remove() 方法进行删除操作。Java 迭代器是一种单向遍历机制,即只能从前往后遍历集合中的元素,不能往回遍历。 Object 类位于 java.lang 包中,编译时会自动导入,我们创建一个类时,如果没有明确继承一个父类,那么它就会自动继承 Object,成为 Object 的子类。 泛型generics的本质是类型参数化,也就是说所操作的数据类型被指定为一个参数。 泛型方法 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的 )。public static < E > void printArray( E[] inputArray ){} 一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。java 中泛型标记符:E - Element (在集合中使用,因为集合中存放的是元素) T - Type(Java 类) K - Key(键) V - Value(值) N - Number(数值类型) ? - 表示不确定的 java 类型 (?和T的区别是啥?这是个有点复杂的问题)。。。。。。答案是只是约定惯例而已,不是语法,你写啥字母都行。。。shit 要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界。public static > T maximum(T x, T y, T z){} (感觉是限定了类型需要继承自某个接口或类,所以叫上界) 泛型类 泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。public class Box {} 类型通配符一般是使用 ? 代替具体的类型参数。例如 List 在逻辑上是 List,List 等所有 List<具体类型实参> 的父类。 类型通配符下限通过形如 List 来定义,表示类型只能接受 Number 及其上层父类类型,如 Object 类型的实例。 序列化在 Java 中是通过 java.io.Serializable 接口来实现的,该接口没有任何方法,只是一个标记接口,用于标识类可以被序列化。当序列化一个对象到文件时, 按照 Java 的标准约定是给文件一个 .ser 扩展名。如果有一个属性不是可序列化的,则该属性必须注明是短暂的transient。 Java 网络编程 Socket 编程 服务器实例化一个 ServerSocket 对象,调用accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。 客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。 Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。 在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。 连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。 服务器应用程序通过使用 java.net.ServerSocket 类以获取一个端口,并且侦听客户端请求。java.net.Socket 类代表客户端和服务器都用来互相沟通的套接字。客户端要获取一个 Socket 对象通过实例化 ,而 服务器获得一个 Socket 对象则通过 accept() 方法的返回值。 Java 提供了三种创建线程的方法:通过实现 Runnable 接口;通过继承 Thread 类本身;通过 Callable 和 Future 创建线程。1. 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。 Applet 是一种 Java 程序。它一般运行在支持 Java 的 Web 浏览器内。因为它有完整的 Java API支持,所以Applet 是一个全功能的 Java 应用程序。 Applet作为页面插件技术,不用多说,连 flash 都快被淘汰了,更无论从未流行的 applet。可以不用学了。。。。。。有JS代替。 和applet前端不同,servlet是后端并且和容器有关得需要精通。 文档注释允许你在程序中嵌入关于程序的信息。javadoc 工具将你 Java 程序的源代码作为输入,输出一些包含你程序注释的HTML文件。 import导入的是class文件,jar包就是个zip包,里边包含的是class文件而不一定包含源码java文件。 MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式。这种模式用于应用程序的分层开发。 Model(模型) - 模型代表一个存取数据的对象或 JAVA POJO。它也可以带有逻辑,在数据变化时更新控制器。 View(视图) - 视图代表模型包含的数据的可视化。 Controller(控制器) - 控制器作用于模型和视图上。它控制数据流向模型对象,并在数据变化时更新视图。它使视图与模型分离开。 Java中的静态类被称为静态,是因为被static修饰的成员变量和成员方法在编译后所分配的内存会一直存在,直到程序退出内存才会释放这个空间。‌ 被static修饰的成员变量和成员方法属于类本身,在类装载的时候被装载到内存,不自动进行销毁,会一直存在于内存中,直到JVM关闭‌。 -------------------------------------------函数。。。。。。------------------------------------------------------- Supplier、Function、Predicate、Consumer、BiFunction、BiPredicate、BiConsumer是Java函数式接口的一部分,它们用于定义不同类型的函数,从而在函数式编程中提供了更灵活的方式来处理数据。 这些函数式接口可以通过Lambda表达式来实现,从而简化代码的编写。在函数式编程中,它们可以作为方法的参数或返回值,用于描述不同的行为和操作,提高代码的可读性和可维护性。 方法引用有四种主要形式:静态方法引用、实例方法引用(特定对象或任意对象)、以及构造方法引用。它们是简化 Lambda 表达式的方式。 ---------------------------------------范型------------------------------------------------------------------------ 从效果上来讲,范型只在编译阶段有效(实际是因为版本兼容考虑做了类型擦除。。。。。。运行时不会检查类型) 编译器在编译阶段检查范型结果之后,就会将范型信息删除。范型信息不会进入运行时阶段。 泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。 Java 范型(Generics)是 JDK 5 引入的一个新特性,用于在编译时提供编译时类型安全检查。 范型的主要目的是为了保证集合中的数据类型安全。泛型不仅仅是为了类型安全,它还可以提高代码的重用性和可读性。 范型三种使用方法:范型类、范型接口、范型方法。范型方法是在返回值前面声明,类和接口是在名称后边声明。 定义范型类不一定要传入范型类型实参。Generic generic = new Generic();// <>中什么都不传入,等价于Generic generic = new Generic<>(); 实现泛型接口时没有确定类型参数,则默认为Object。 在继承泛型接口时,必须确定泛型接口的类型参数。 public static coid printArray(E[] array)一般被称为静态泛型方法;静态变量和静态方法在类加载时已经初始化,直接使用类名调用。在泛型类的类型参数未确定时,静态成员有可能被调用,因此泛型类的类型参数是不能在静态成员中使用的。因此静态方法无法使用类上的泛型,只能使用自己的泛型。 *************泛型的编译(擦除机制) 擦除机制的实质就是,在编译阶段,Java的泛型类型可能是ArrayList但是在java文件编译成字节码的过程中,泛型参数部分就被擦出了(泛型类,泛型方法的参数全部被替换成它的第一个上界或者顶级父类Object),在class文件中,无论参数是什么,JVM实际执行的代码类型其实是ArrayList类型,这也就引出了很多问题如下: 泛型参数只能是引用类型而不能是基本数据类型,因为基本数据类型无法被擦除成Object。 不能使用instanceof关键字进行泛型类型检测,因为在运行时所以的泛型类型都是裸类型。 泛型类型无法实例化类型参数T a=new T(),因为在运行时无法确定T的具体类型,也不知道T是否存在无参构造器。 无法实例化泛型数组T[] arry =new T[2];因为泛型最后都被擦除成Object数组,在使用时很容易发生类型转化异常,比如object转化不成string。 擦除机制是Java为了引入泛型这个语法而不得不做出的妥协之举,泛型语法是JDK5之后引入的,为了兼容老版本,不得不在编译阶段将泛型擦除成裸类型。但是在其他语言中,泛型的使用会非常自然且简单安全,在编写代码是我们要了解泛型擦除机制,否则可能会引发很多不必要的异常。 类型擦除是指在运行时对于JVM而言泛型参数被擦除掉了,并不代表泛型信息消失了,才class文件中泛型信息被以其他方式进行保存,我们依然可以在运行时通过反射的手段进行泛型类型检测。 擦除机制的实质就是,在编译阶段,Java的泛型类型可能是ArrayList但是在java文件编译成字节码的过程中,泛型参数部分就被擦出了(泛型类,泛型方法的参数全部被替换成它的第一个上界或者顶级父类Object),在class文件中,无论参数是什么,JVM实际执行的代码类型其实是ArrayList类型 子类的泛型类型是无法使用父类的泛型类型接收的 Java泛型的上下界包括本身‌。 Java 范型的上下界设计是为了在编译时确保类型安全。 通配符上下限是为了把运行问题暴露在编译阶段。。。。。。 上下界通配符getset限制最好的理解方法是:extends上界get和super下界set肯定不会出现父转子情况,编译器为了防止出现父转子的情况直接把可能出错的部分禁了。(按不能getset理解多绕了一层) 上界通配符所表示的是一个具体类型,而非一类类型。只是我们不知道这个具体类型叫什么名字而已。我们假装给他取一个代号,这个具体类型是A,A是Student的子类。那么一个List,你可以向里面放什么类型合适呢?答案只有A,在java中就连A的子类都是不行的。举个例子,在代码运行时,Plate p 既可以接收new Plate(),但它也可以被赋值为 new Plate()。若运行时被赋值的是后者,那么p就不能set(new Apple())了,所以干脆就在编译时不允许这样写。”?“通配符修饰的对象只能接收,不能修改,也就是不能设置。 上界 ,表示所有继承Fruit的子类,但是具体是哪个子类,无法确定,所以调用add的时候,要add什么类型,谁也不知道。但是get的时候,不管是什么子类,不管追溯多少辈,肯定有个父类是Fruit,所以,我都可以用最大的父类Fruit接着,也就是把所有的子类向上转型为Fruit。 下界 ,表示Apple的所有父类,包括Fruit,一直可以追溯到老祖宗Object 。那么当我add的时候,我不能add Apple的父类,因为不能确定List里面存放的到底是哪个父类。但是我可以add Apple及其子类。因为不管我的子类是什么类型,它都可以向上转型为Apple及其所有的父类甚至转型为Object 。但是当我get的时候,Apple的父类这么多,我用什么接着呢,除了Object,其他的都接不住。 编译看左,运行看右。 >它代表的意思是:类型T必须实现Comparable接口,并且这个接口的类型是T或者是T的任一父类。这样声明后,T的实例之间和T的父类的实例之间可以相互比较大小。 ---------------------------------------------------------------------------------------------------------------------- JVM将类的加载分为3个步骤: 1、加载(Load):class文件创建Class对象。 2、链接(Link) 3、初始化(Initialize) 其中 链接(Link)又分3个步骤,如下图所示: 类什么时候才被初始化: 1)创建类的实例,也就是new一个对象 2)访问某个类或接口的静态变量,或者对该静态变量赋值 或者调用类的静态方法(注意:访问常量不会触发) 3)反射(Class.forName("com.lyj.load")) (注意:classLoader.loadClass只动态装载,不会初始化) 4)初始化一个类的子类(会首先初始化子类的父类) 5)JVM启动时标明的启动类. 注意:   1、类加载不一定会初始化。一个类只加载一次。   2、在初始化一个类或接口时,并不会先初始化它所实现的接口。   3、如果静态方法或变量在parent中定义,从子类进行调用,则不会初始化子类。 ---------------------------------------------------------------------------------------------------------------------- Java语言系统自带有三个类加载器: - Bootstrap ClassLoader 最顶层的加载类,主要加载核心类库,%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等。 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - Extention ClassLoader 扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还可以加载-D java.ext.dirs选项指定的目录。 Java9带来了模块化系统,ExtClassLoader消失了且多了PlatformClassLoader xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - Appclass Loader也称为SystemAppClass 加载当前应用的classpath的所有类。 BootstrapClassLoader、ExtClassLoader、AppClassLoader实际是查阅相应的环境属性sun.boot.class.path、java.ext.dirs和java.class.path来加载资源文件的。 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 我们通常说在jdk中,默认有三个类加载器,bootstrapClassLoader、PlatformClassLoader、AppClassLoader,我们说前者是后者的父类加载器,但是实际上,这里所谓的父类加载器,并不是Java中的父子类继承关系,而是说: AppClassLoader中有一个parentClassLoader设置的值是ExtClassLoader PlatformClassLoader中的parentClassLoader设置的是bootstrapClassLoader 我们自定义的类加载器,对应的parentClassLoader是AppClassLoader 我们如果自定义一个类加载器,默认设置的父类加载器是AppClassLoader,这是因为jre中用来启动main()方法的入口类Launcher构造函数Launcher()中在初始化自定义类加载器的时候,会指定其parentClassLoader为AppClassLoader。 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 双亲委托 如果当前的类加载器没有查询到这个class对象已经加载就请求父加载器(不一定是父类)进行操作,然后以此类推。 直到Bootstrap ClassLoader。如果Bootstrap ClassLoader也没有加载过此class实例,那么它就会从它指定的路径中去查找,如果查找成功则返回, 如果没有查找成功则交给子类加载器,也就是ExtClassLoader,这样类似操作直到终点,也就是我上图中的红色箭头示例。 用序列描述一下: 1. 一个AppClassLoader查找资源时,先看看缓存是否有,缓存有从缓存中获取,否则委托给父加载器。 2. 递归,重复第1部的操作。 3. 如果PlatformClassLoader也没有加载过,则由Bootstrap ClassLoader出面,它首先查找缓存,如果没有找到的话,就去找自己的规定的路径下,也就是sun.mic.boot.class下面的路径。找到就返回,没有找到,让子加载器自己去找。 4. Bootstrap ClassLoader如果没有查找成功,则ExtClassLoader自己在java.ext.dirs路径中去查找,查找成功就返回,查找不成功,再向下让子加载器找。 5. PlatformClassLoader查找不成功,AppClassLoader就自己查找,在java.class.path路径下查找。找到就返回。如果没有找到就让子类找,如果没有子类会怎么样?抛出各种异常。 自定义类加载器:继承ClassLoader,覆盖findClass()方法。 ContextClassLoader。我们不能使用当前类加载器的子加载器加载的类。这个限制就是双亲委派机制导致的,因为类加载请求的委派是单向的。为了解决这个问题,引入了线程上下文类加载器。通过java.lang.Thread类的setContextClassLoader()设置当前线程的上下文类加载器(如果没有设置,默认会从父线程中继承,如果程序没有设置过,则默认是System类加载器)。有了线程上下文类加载器,应用程序就可以通过java.lang.Thread.setContextClassLoader()将应用程序使用的类加载器传递给使用更顶层类加载器的代码。,引入线程类加载器实际是对双亲委派机制的破坏,但是却提供了类加载的灵活性。 ContextClassLoader其实只是一个概念,查看Thread.java源码可以发现contextClassLoader只是一个成员变量,通过setContextClassLoader()方法设置,通过getContextClassLoader()设置。 public class Thread implements Runnable { /* The context ClassLoader for this thread */ private ClassLoader contextClassLoader; public void setContextClassLoader(ClassLoader cl) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(new RuntimePermission("setContextClassLoader")); } contextClassLoader = cl; } public ClassLoader getContextClassLoader() { if (contextClassLoader == null) return null; SecurityManager sm = System.getSecurityManager(); if (sm != null) { ClassLoader.checkClassLoaderPermission(contextClassLoader, Reflection.getCallerClass()); } return contextClassLoader; } } ---------------------------------------clone----------------------------------------------------------- 1 public class Object { 2 protected native Object clone() throws CloneNotSupportedException; 3 } 由源代码我们会发现: 第一:Object类的clone()方法是一个native方法,native方法的效率一般来说都是远高于Java中的非native方法。这也解释了为什么要用Object中clone()方法而不是先new一个类,然后把原始对象中的信息复制到新对象中,虽然这也实现了clone功能。(JNI是Java Native Interface的 缩写。从Java 1.1开始,Java Native Interface (JNI)标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的,比如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少保证本地代码能工作在任何Java 虚拟机实现下。) 第二:Object类中的 clone()方法被protected修饰符修饰。这也意味着如果要应用 clone()方 法,必须继承Object类,在 Java中所有的类是缺省继承 Object类的,也就不用关心这点了。然后重载 clone()方法。还有一点要考虑的是为了让其它类能调用这个 clone类的 clone()方法,重载之后要把 clone()方法的属性设置为 public。 第三:Object.clone()方法返回一个Object对象。我们必须进行强制类型转换才能得到我们需要的类型。 浅层复制与深层复制概念:   浅层复制: 被复制的对象的所有成员属性都有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅层复制仅仅复制所考虑的对象,而不复制它所引用的对象。(概念不好理解,请结合下文的示例去理解)   深层复制:被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不是原有的那些被引用的对象。换言之,深层复制要复制的对象引用的对象都复制一遍。 Java中对象的克隆   1)在派生类中实现Cloneable借口。   2)为了获取对象的一份拷贝,我们可以利用Object类的clone方法。   3)在派生类中覆盖积累的clone方法,声明为public。 @Override public Object clone(){ Object o=null; try { o=super.clone();//Object中的clone()识别出你要复制的是哪一个对象。 } catch (CloneNotSupportedException e) { e.printStackTrace(); } return o; }   4)在派生类的clone方法中,调用super.clone()。 Student s1=new Student("zhangsan"); Student s2=(Student)s1.clone(); 实现Cloneable接口   首先,看一下源码:   1 public interface Cloneable { 2 }   我们奇怪的发现Cloneable竟然是空的,那么我们为什么要实现Cloneable接口呢?其实Cloneable接口仅仅是一个标志,而且这个标志也仅仅是针对 Object类中 clone()方法的,如果 clone 类没有实现 Cloneable 接口,并调用了 Object 的 clone() 方法(也就是调用了 super.Clone() 方法),那么Object 的 clone() 方法就会抛出 CloneNotSupportedException 异常。   1)为什么我们在派生类中覆盖Object的clone()方法时,一定要调用super.clone()呢?在运行时刻,Object中的clone()识别你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。   2)继承自java.lang.Object.clone()方法是浅层复制。 那么我们如何实现深层复制的克隆? 1.在@Override clone()返回object之前调用对象成员的clone方法填充object后再返回。 o=(Student)super.clone(); o.pro=(Professor)pro.clone(); return o; 2.利用串行化来实现深层复制。应当指出的是,写在流中的是对象的一个拷贝,而原来对象仍然存在JVM里面。 public Object deepClone() throws IOException, ClassNotFoundException{ //将对象写到流中 ByteArrayOutputStream bo=new ByteArrayOutputStream(); ObjectOutputStream oo=new ObjectOutputStream(bo); oo.writeObject(this); //从流中读出来 ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray()); ObjectInputStream oi=new ObjectInputStream(bi); return oi.readObject(); } ------------------------------------------------------------------------------------------------------- ------------------------------------------各种奇葩的写法和结构--------------------------------------------------------- 单例模式 匿名内部类----------------lambda表达式 内部类 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 单例模式:把构造函数改称private隐藏起来。。。。。。开放一个static方法暴露内部维护的一个自身的对象。。。 懒汉式:初始化没加载,调用public函数才加载,可能会出现多线程锁死问题。要避免有几种方法: 1.synchronized锁加到public函数上,如果用双检锁可以提升性能,双检索是用简单的null判断代替了大部分情况下的synchronized锁所以提升了性能。可以达到动态域lazyloding。 2.静态内部类。可以达到静态域lazyloding。 3.枚举。 俄汉式:初始化加载,利用classloader避免多线程锁死问题。(俄汉,自然没法lazyloading。。。。。。) 枚举:很好的单例实现方式。 登记式单例:由于以上模式的构造方法是私有的,不可继承,Spring为实现单例类可继承,使用的是单例注册表的方式(登记式单例)。 使用map实现注册表; 使用protect修饰构造方法; 使得单例对继承开放。 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 匿名内部类:个人一点理解,一个类如果是匿名的则必然是内部的,为啥?因为如果写在外部永远没法使用到这个类,这个类是个孤岛。。。只有在定义的同时去使用才能用到它,而一个类写在外部是没法同时使用他的。。。因为没有名字,你没法在定义完了之后指向他。。。就像广场里边很多人,而一个人如果没有名字那只能是在你面前的时候你才能叫他:嘿~哥们儿。。。。。。 使用new关键字后跟@Override注解的写法,@Override注解用于明确表示某个方法是重写父类或接口中的方法,这种写法表明你正在创建一个匿名类,该类实现了某个接口或继承了某个类,并且重写了其中的方法‌。 借助接口或抽象类new的同时实现方法。作用就是为了简化代码。使用的时候其实本身是一个对象,因为使用的时候必然同时new。。。。。。 函数式接口的精确定义:只有一个抽象方法的接口。(可以用@FunctionalInterface来验证) 当接口是函数式接口的时候,可以用lambda表达式实现。所以可以说lambda表达式是匿名内部类的一种特殊情况,匿名内部类可以为抽象类、甚至普通类创建实例,但Lambda表达式只能为函数式接口创建实例。匿名内部类实现的抽象方法的方法体允许调用接口中定义的默认方法;但Lambda表达式的代码块不允许调用接口中定义的默认方法。Lambda表达式算是一个匿名函数(只有参数和函数体,没有函数名),且是一个对象(可以引用给一个函数式接口的对象)。 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 内部类作用 1.内部类可以很好的实现隐藏,一般的非内部类,是不允许有 private 与protected权限的,但内部类可以 2.逻辑分组更好的可读性和可维护性 3.可实现多重继承。。。。。。 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++