首页 文章 精选 留言 我的

精选列表

搜索[语法],共10006篇文章
优秀的个人博客,低调大师

Java8之Lambda语法

我又回来了 已经很久没有写过博客了。 因为前段时间我感到自己之前写的博客毫无深度,像是一个产品的说明书,而这样的博客会将一项技术看作黑匣子——你不需要知道这个技术的原理或源代码的实现逻辑,你只需要按照接口的说明调接口去完成你要实现的功能就够了。 这样将某一项IT技术看作黑匣子,以简单的利用它的功能实现自己想要的功能为目标的想法在实际工作中是合理的,因为实际工作是讲究效率的,没有那么多时间让你拨开面纱悟其内核。但是,作为一名开发者,岂能止步于此?至少在工作之外的时间中,将那个“黑匣子”打开看看它的内部,在下次使用它的时候,让这个“黑匣子”在你的手里可以由自己完全定制,并以这个流行的技术为鉴,择其善者而从之,则其不善者而改之。如此,不断进步。 我一直想写有深度的技术博客,但我可能一直都对深度这个概念有些误解,到底何为深度?之前的我一直将深度这个词和复杂联系在一起,认为只有将复杂的东西吃透了,才叫深度;但是我现在才意识到,自己对复杂这个概念也有误解——到底何为复杂?Java基础简单吗?肯定会有不少人质疑:Java基础不简单吗?几个基本变量、控制语句、类、接口、集合等等,都是很简单的东西。但是如果我问你,这些东西都是怎么实现的?应该会有很多人哑口无言。我认为简单与复杂的关系,一方面是相对的,即之前看上去复杂的东西掌握了之后就会看上去简单;另一方面就是深度,即之前看上去简单的东西追求深度之后就会看上去复杂。而简单的东西是怎么实现的,就是深度。 我将重拾博客,写一些有深度的技术博客,这样,也好在这个碎片化的时代,让自己的技术不那么碎片化,不停留于片面,养成深度思考的习惯。 关于Java的新功能的博客,网络上还真的不少,但是不够系统也不够全面,我将这些东西整理后写成博客,为想要对Java8的新特性学习的朋友提供参考,共同学习。 我将连续写Java8相比之前版本的新功能,由此成为一个系列——话说Java8还真是一个呈上启下的版本。 行为参数化 所谓行为参数化,就是将行为作为参数传入函数。 比如下面这个接口(通过作者筛选图书): List<Book> selectBookdByAuth(String authName); 我们想要获取“路遥”的书,我们需要将“路遥”作为参数传入selectBookdByAuth这个函数。 但是,如果我又想根据出版社筛选图书呢?那就又要创建一个根据出版社筛选图书的接口了。那如果我又有需求了呢?要根据图书类别筛选图书…… 有没有什么办法把“我想要根据什么筛选图书”作为一个参数呢?这样我们只需要一个筛选图书的接口就可以完成各种筛选图书的功能了。 在这里,“我想要根据什么筛选图书”是就是一个行为,将“我想要根据什么筛选图书”作为参数传入相应的函数,就被称为行为参数化。 我们先举个例子(筛选图书的接口): List<Book> selectBook(Predicate<Book> predicate); 我们暂且不用思考 Predicate是什么,当我们创建了这个接口之后,我们就可以将行为作为参数传入该函数了。 例如我想要获取以“路遥”为作者的图书: List<Book> books = selectBook(boook -> book.getAuth().equals("路遥")); 就可以获取到想要的结果了,再例如我想要获取以“人民邮电出版社”为出版社的图书: List<Book> books = selectBook(boook -> book.getPress().equals("人民邮电出版社")); 函数式 函数式接口 函数式接口就是只定义一个抽象方法的接口。 例如: /** * 运算函数式接口. * * @author zuoyu * **/ public interface Operation { /** * 用于运算两个int类型的数 * @param a - 参数一 * @param b - 参数二 * @return - 结果 */ int opera(int a, int b); } 对这个接口的简单使用: @Test public void operationTest() { Operation operation = (a, b) -> a + b; int result = operation.opera(1, 1); System.out.println(result); } //result:2 函数描述符 函数式接口的抽象方法的签名(参数、返回值)就是Lambda表达式的签名。其中抽象方法就是函数描述符。 例如上面的int opera(int a, int b);可以接受的Lambda表达式为(a, b) -> a + b,那么其中的参数a和参数b都是int类型,a + b结果也为int,那么这个函数的签名就是(int, int) -> int。 再打个比方,例如刚才筛选图书的函数式接口Predicate: public interface Predicate<T> { boolean test(T t); } 那么它的函数签名就是T -> boolean,意味着我们将类型T的对象作为参数传入,返回boolean类型。只有符合函数描述符的Lambda表达式才能作为参数传入相应的函数。 使用函数式接口 Java API已经为我们提供了很常用的函数式接口以及其函数描述符,当这些函数式接口不够我们使用的时候我们也可以自己创建。(一定要记住,一个函数式本身并没有什么意义,其意义在于其函数签名。) 拿几个函数式接口细说一下: Predicate<T> public interface Predicate<T> { boolean test(T t); } java.util.function.Predicate<T>接口定义了一个名为test的抽象方法,它接受泛型T对象,并返回一个boolean。在你需要一个涉及到类型T的布尔表达式时,就可以使用这个函数式接口。 例如你可以写一个过滤List集合元素的方法,将这个函数式接口作为参数: /** * Predicate<T>接口 * * @param list - 集合 * @param predicate - 筛选条件 * @param <T> - 类型 * @return 符合要求的结果 */ public static <T> List<T> filter(List<T> list, Predicate<T> predicate) { List<T> results = new ArrayList<>(); list.forEach(t -> { if (predicate.test(t)) { results.add(t); } }); return results; } 这是一个通用的对List集合进行元素过滤的方法: 从一个图书List集合里获取以“图灵出版社”为出版社的图书: List<Book> pressBooks = filter(books, boook -> book.getPress().equals("图灵出版社")); 注:在这里,泛型T就是Book类型 从一个苹果集合内获取重量大于1.2的苹果: List<Apple> weightApples = filter(apples, apple -> apple.getwWight() > 1.2D); 注:在这里泛型T就是Apple类型 Consumer<T> public interface Consumer<T> { void accept(T t); } java.util.function.Consumer<T>定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void)。如果你需要访问类型为T的对象,并对其执行某些操作,就可以使用这个接口。 例如我要对一个List集合内的每个元素执行行为操作: /** * 对集合进行任何行为操作 */ public static <T> void action(List<T> list, Consumer<T> consumer) { for (T t : list) { consumer.accept(t); } } 这是一个通用的对List集合进行元素行为操作的方法: 对图书集合内的元素进行打印: action(books, book -> System.out.println(book.toString())); 注:在这里,泛型T就是Book类型 将苹果集合内的每个苹果的重量增加0.5: action(apples, apple -> { apple.setWeight(apple.getWeight() + 1.00D); }); 注:在这里泛型T就是Apple类型 Function public interface Function<T, R> { R apply(T t); } java.util.function.Function<T, R>接口定义了一个叫做apply的方法,它接受一个泛型T的对象,并返回一个泛型R的对象。如果你需要定一个Lambda用于输入对象的信息映射到输出,你便可以利用这个接口来完成。 例如我要对一个List集合内所有对象的某一个属性进行提取: /** * 对集合内对象的某一元素进行提取 */ public static <T, R> List<R> function(List<T> list, Function<T, R> function) { List<R> rList = new ArrayList<>(); for (T t : list) { R r = function.apply(t); rList.add(r); } return rList; } 这是一个通用的对List集合的对象操作的方法: 对图书集合内的所有书的书名进行提取: List<String> booksName = function(books, book -> book.getName()); 注:在这里,泛型T是Book类型,泛型R是String类型 对苹果集合内的所有苹果的重量进行提取,并增加0.5: List<Double> appleWeight = function(apples, apple -> apple.getWeight() + 0.5D ); 注:在这里,泛型T是Apple类型,泛型R是Double类型 Function的原始类型化 众所周知,在Java中的泛型只能绑定到引用类型上,不能绑定在原始类型上。在Java中有一个将原始类型转换为对应的引用类型的机制——装箱;相反的将引用类型转换为原始类型的机制——拆箱。这一系列操作都是Java自动完成的,但是这个机制是要付出代价的—— 装箱:把原始数据类型包裹起来,并保存到堆里。所以,装箱后需要更多的内存来保存,并需要额外的内存用来搜索并获取被包裹的原始值。 Java8为避免这个现象对其所提供的函数式接口带来了一个专门版本,在数据的输入和输出都是原始类型时避免自动装箱的操作,以此节省内存。 例如我要根据下标获取一个苹果对象: IntFunction<Apple> appleIntFunction = (int i) -> apples.get(i); Apple apple = appleIntFunction.apply(2); 我们来看一下IntFunction接口: public interface IntFunction<R> { R apply(int value); } java.util.function.IntFunction<R>接口的参数为int原始类型,返回一个R类型,与我们想要完成相同功能的java.util.function.Function<T, R>接口相比较,避免了必须传入Integer类型的自动装箱操作。 再比如我要获取一个double随机数的2倍数: IntToDoubleFunction intToDoubleFunction = (int i) -> Math.random() * i; double random = intToDoubleFunction.applyAsDouble(2); 我们看一下IntToDoubleFunction接口: public interface IntToDoubleFunction { double applyAsDouble(int value); } 上面的功能如果我们使用java.util.function.Function<T, R>接口来实现这个功能,需要将接口写成Function<Integer, Double>,输入Integer类型并输出Double类型;相对于java.util.function.IntToDoubleFunction接口,输入int类型输出double类型,省去了自动装箱。 Function的变种函数: IntFunction<R> IntToDoubleFunction IntToLongFunction LongFunction<R> LongToDoubleFunction LongToIntFunction DoubleFunction<R> ToIntFunction<T> ToDoubleFunction<T> ToLongFunction<T> 其他函数式接口 JavaAPI自带的函数接口还有不少,为的是我们日常使用。当然也有不能满足我们需求的时候,比如我要输入三个参数,那就需要自己定义接口了。还是那句话,函数接口本身并无意义,其意义在于其函数签名(参数数量与返回类型)。 JavaAPI自带的函数式接口(不一一细说了): 函数式接口 函数描述符 Predicate<T> T -> boolean Consumer<T> T -> void Function<T, R> T -> R Supplier<T> (void) -> T UnaryOperator<T> T -> T BinaryOperator<T> (T, T) -> T BiPredicate<L, R> (L, R) -> boolean BiConsumer<T, U> (T, U) -> void BitFunction<T, U, R> (T, U) -> R 注:以上函数式接口都有其原始类型化的变种。 方法引用 方法引用可以重复的使用现有的方法定义,可以将其理解为Lambda的简化方式。 例如,我要根据苹果的重量对其从小到大排序: apples.sort((apple1, apple2) -> apple1.getWeight().compareTo(apple2.getWeight())); 上面是Lambda表达式的写法,那么换作方法引用的方式可以简化代码: apples.sort(Comparator.comparing(Apple::getWeight)); 相对于Lambda表达式的写法,方法引用的写法在这里意思更加清晰直观。 方法引用主要有三类: 指向静态方法的方法引用(例:Integer的parseInt()方法可以直接写成Integer::parseInt)。 指向任意类型实例方法的方法引用(例:String的length()方法可以直接写成String::length)。 指向现有对象的实例方法的方法引用(例:假设有一个局部变量book指向用于存放Book类型的对象,它有一个实例方法getName(),你可以直接写成book::getName)。 复合Lambda表达式 Java8API中的函数式接口都提供了复合方法,即通过这些方法把多个简单的Lambda表达式复合成复杂的表达式。 谓词复合 谓词接口包含三个方法:negate(否定)、and(并且)、or(或)。 这几个谓词类似布尔语句之间的关系,举个例子: 比如我现在有三种对图书的筛选方案: 筛选出以“路遥”为作者的图书的逻辑接口: Predicate<Books> bookPredicateByAuth = book -> book.getAuth().equals("路遥")); 筛选出以“人民邮电出版社”为出版社的图书的逻辑接口: Predicate<Books> bookPredicateByPress = boook -> book.getPress().equals("人民邮电出版社")); 筛选出印刷时间在2010年之后的图书的逻辑接口: Predicate<Books> bookPredicateByPrintingData = boook -> book.getPrintingData.before(new SimpleDateFormat("yyyy-MM-dd").parse("2010-01-01");); 那么我现在想要筛选出以“路遥”为作者的,印刷时间在2010年之后的图书,不要以“人民邮电出版社”出版的,可以该逻辑接口这么写: Predicate<Books> bookPredicate = bookPredicateByAuth.and(bookPredicateByPrintingData).negate(bookPredicateByPress); 进行筛选: List<Book> books = filter(books, bookPredicate)); 函数复合 函数复合的接口方法有两个:andThen和compose。 andThen方法会返回一个函数,它先对输入应用一个给定的函数,再对输出应用另一个函数。 compose方法把给定的函数作用compose的参数里面给的那个函数,然后再把函数本身用于结果。 这两个函数的作用就是函数套函数,举个例子: 我现在有两个函数: 第一个函数为两数相加的函数: Function<Integer, Integer> add = x -> x + 1; 第二个为两数相乘的函数: Function<Integer, Integer> multiply = x -> x * 2; 如果想要先算加法再算乘法,达到multiply(add())的效果(只是可以这样理解,实际不是这样的结构): Function<Integer, Integer> function = add.andThen(multiply); function.apply(1); // result: 4 如果想要先算乘法再算加法,达到add(multiply)的效果(只是可以这样理解,实际不是这样的结构): Function<Integer, Integer> function = add.compose(multiply); function.apply(1); // result: 3 结尾 转载请表明出处 下期咱们聊一聊Java8的stream流

优秀的个人博客,低调大师

常用的ES6语法

什么是ES6? ECMAScript 6(以下简称ES6)是JavaScript语言的下一代标准,已经在2015年6月正式发布了。Mozilla公司将在这个标准的基础上,推出JavaScript 2.0。 ECMAScript和JavaScript到底是什么关系?很多初学者会感到困惑,简单来说,ECMAScript是JavaScript语言的国际标准,JavaScript是ECMAScript的实现。 一、let和const 在JavaScript中咱们以前主要用关键var来定义变量,ES6之后,新增了定义变量的两个关键字,分别是let和const。 对于变量来说,在ES5中var定义的变量会提升到作用域中所有的函数与语句前面,而ES6中let定义的变量则不会,let声明的变量会在其相应的代码块中建立一个暂时性死区,直至变量被声明。 let和const都能够声明块级作用域,用法和var是类似的,let的特点是不会变量提升,而是被锁在当前块中。 一个非常简单的例子: function test() { if(true) { console.log(a)//TDZ,俗称临时死区,用来描述变量不提升的现象 let a = 1 } } test() // a is not defined function test() { if(true) { let a = 1 } console.log(a) } test() // a is not defined 唯一正确的使用方法:先声明,再访问。 function test() { if(true) { let a = 1 console.log(a) } } test() // 1 const 声明常量,一旦声明,不可更改,而且常量必须初始化赋值。 const虽然是常量,不允许修改默认赋值,但如果定义的是对象Object,那么可以修改对象内部的属性值。 const type = { a: 1 } type.a = 2 //没有直接修改type的值,而是修改type.a的属性值,这是允许的。 console.log(type) // {a: 2} const和let的异同点 相同点:const和let都是在当前块内有效,执行到块外会被销毁,也不存在变量提升(TDZ),不能重复声明。 不同点:const不能再赋值,let声明的变量可以重复赋值。 const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。 块级作用域的使用场景 除了上面提到的常用声明方式,我们还可以在循环中使用,最出名的一道面试题:循环中定时器闭包的考题 在for循环中使用var声明的循环变量,会跳出循环体污染当前的函数。 for(var i = 0; i < 5; i++) { setTimeout(() => { console.log(i) //5, 5, 5, 5, 5 }, 0) } console.log(i) //5 i跳出循环体污染外部函数 //将var改成let之后 for(let i = 0; i < 5; i++) { setTimeout(() => { console.log(i) // 0,1,2,3,4 }, 0) } 前端全栈学习交流圈:866109386,面向1-3经验年前端开发人员,帮助突破技术瓶颈,提升思维能力,群内有大量PDF可供自取,更有干货实战项目视频进群免费领取。 console.log(i)//i is not defined i无法污染外部函数 在实际开发中,我们选择使用var、let还是const,取决于我们的变量是不是需要更新,通常我们希望变量保证不被恶意修改,而使用大量的const。使用const声明,声明一个对象的时候,也推荐使用const,当你需要修改声明的变量值时,使用let,var能用的场景都可以使用let替代。 symbol ES6 以前,我们知道5种基本数据类型分别是Undefined,Null,Boolean,Number以及String,然后加上一种引用类型Object构成了JavaScript中所有的数据类型,但是ES6出来之后,新增了一种数据类型,名叫symbol,像它的名字表露的一样,意味着独一无二,意思是每个 Symbol类型都是独一无二的,不与其它 Symbol 重复。 可以通过调用 Symbol() 方法将创建一个新的 Symbol 类型的值,这个值独一无二,不与任何值相等。 var mySymbol=Symbol(); console.log(typeof mySymbol) //"symbol" 二、字符串 ES6字符串新增的方法 UTF-16码位:ES6强制使用UTF-16字符串编码。关于UTF-16的解释请自行百度了解。 codePointAt():该方法支持UTF-16,接受编码单元的位置而非字符串位置作为参数,返回与字符串中给定位置对应的码位,即一个整数值。 String.fromCodePoiont():作用与codePointAt相反,检索字符串中某个字符的码位,也可以根据指定的码位生成一个字符。 normalize():提供Unicode的标准形式,接受一个可选的字符串参数,指明应用某种Unicode标准形式。 在ES6中,新增了3个新方法。每个方法都接收2个参数,需要检测的子字符串,以及开始匹配的索引位置。 模板字符串 字符串是JavaScript中基本类型之一,应该算是除了对象之外是使用最为频繁的类型吧,字符串中包含了例如substr,replace,indexOf,slice等等诸多方法,ES6引入了模板字符串的特性,用反引号来表示,可以表示多行字符串以及做到文本插值(利用模板占位符)。 // 以前的多行字符串我们这么写: console.log("hello world 1\n\ hello cala"); // "hello world // hello cala" //有了模板字符串之后 console.log(`hello world string text line 2`); // "hello world // hello cala" 可以用${}来表示模板占位符,可以将你已经定义好的变量传进括弧中,例如: var name="cala"; var age=22; console.log(`hello,I'am ${name},my age is ${age}`) //hello,I'am cala,my age is 22 includes(str, index):如果在字符串中检测到指定文本,返回true,否则false。 let t = 'abcdefg' if(t.includes('cde')) { console.log(2) } //true startsWith(str, index):如果在字符串起始部分检测到指定文本,返回true,否则返回false。 let t = 'abcdefg' if(t.startsWith('ab')) { console.log(2) } //true endsWith(str, index):如果在字符串的结束部分检测到指定文本,返回true,否则返回false。 let t = 'abcdefg' if(t.endsWith('fg')) { console.log(2) } //true 如果你只是需要匹配字符串中是否包含某子字符串,那么推荐使用新增的方法,如果需要找到匹配字符串的位置,使用indexOf()。 三、函数 函数的默认参数 在ES5中,我们给函数传参数,然后在函数体内设置默认值,如下面这种方式。 function a(num, callback) { num = num || 6 callback = callback || function (data) {console.log('ES5: ', data)} callback(num * num) } a() //ES5: 36,不传参输出默认值 //你还可以这样使用callback a(10, function(data) { console.log(data * 10) // 1000, 传参输出新数值 }) 在ES6中,我们使用新的默认值写法 function a(num = 6, callback = function (data) {console.log('ES6: ', data)}) { callback(num * num) } a() //ES6: 36, 不传参输出默认值 a(10, function(data) { console.log(data * 10) // 1000,传参输出新数值 }) 四、箭头函数(=>) const arr = [5, 10] const s = arr.reduce((sum, item) => sum + item) console.log(s) // 15 箭头函数中this的使用跟普通函数也不一样,在JavaScript的普通函数中,都会有一个自己的this值,主要分为: 普通函数: 1、函数作为全局函数被调用时,this指向全局对象 2、函数作为对象中的方法被调用时,this指向该对象 3、函数作为构造函数的时候,this指向构造函数new出来的新对象 4、还可以通过call,apply,bind改变this的指向 箭头函数: 1、箭头函数没有this,函数内部的this来自于父级最近的非箭头函数,并且不能改变this的指向。 2、箭头函数没有super 3、箭头函数没有arguments 4、箭头函数没有new.target绑定。 5、不能使用new 6、没有原型 7、不支持重复的命名参数。 箭头函数的简单理解 1、箭头函数的左边表示输入的参数,右边表示输出的结果。 const s = a => a console.log(s(2)) // 2 2、在箭头函数中,this属于词法作用域,直接由上下文确定,对于普通函数中指向不定的this,箭头函数中处理this无疑更加简单,如下: //ES5普通函数 function Man(){ this.age=22; return function(){ this.age+1; } } var cala=new Man(); console.log(cala())//undefined //ES6箭头函数 function Man(){ this.age=22; return () => this.age+1; } var cala=new Man(); console.log(cala())//23 前端全栈学习交流圈:866109386,面向1-3经验年前端开发人员,帮助突破技术瓶颈,提升思维能力,群内有大量PDF可供自取,更有干货实战项目视频进群免费领取。 3、箭头函数中没有arguments(我们可以用rest参数替代),也没有原型,也不能使用new 关键字,例如: //没有arguments var foo=(a,b)=>{return arguments[0]*arguments[1]} console.log(foo(3,5)) //arguments is not defined //没有原型 var Obj = () => {}; console.log(Obj.prototype); // undefined //不能使用new 关键字 var Obj = () => {"hello world"}; var o = new Obj(); // TypeError: Obj is not a constructor 4、箭头函数给数组排序 const arr = [10, 50, 30, 40, 20] const s = arr.sort((a, b) => a - b) console.log(s) // [10,20,30,40,50] 尾调用优化 尾调用是指在函数return的时候调用一个新的函数,由于尾调用的实现需要存储到内存中,在一个循环体中,如果存在函数的尾调用,你的内存可能爆满或溢出。 ES6中,引擎会帮你做好尾调用的优化工作,你不需要自己优化,但需要满足下面3个要求: 1、函数不是闭包 2、尾调用是函数最后一条语句 3、尾调用结果作为函数返回 尾调用实际用途——递归函数优化 在ES5时代,我们不推荐使用递归,因为递归会影响性能。 但是有了尾调用优化之后,递归函数的性能有了提升。 //新型尾优化写法 "use strict"; function a(n, p = 1) { if(n <= 1) { return 1 * p } let s = n * p return a(n - 1, s) } //求 1 x 2 x 3的阶乘 let sum = a(3) console.log(sum) // 6 五、ES6对象新增方法 Object.assign() Object.assign()方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。 Object.assign 方法只会拷贝源对象自身的并且可枚举的属性到目标对象。该方法使用源对象的[[Get]]和目标对象的[[Set]],所以它会调用相关 getter 和 setter。因此,它分配属性,而不仅仅是复制或定义新的属性。如果合并源包含getter,这可能使其不适合将新属性合并到原型中。为了将属性定义(包括其可枚举性)复制到原型,应使用Object.getOwnPropertyDescriptor()和Object.defineProperty() 。 String类型和 Symbol 类型的属性都会被拷贝。 合并对象 var o1 = { a: 1 }; var o2 = { b: 2 }; var o3 = { c: 3 }; var obj = Object.assign(o1, o2, o3); console.log(obj); // { a: 1, b: 2, c: 3 } console.log(o1); // { a: 1, b: 2, c: 3 }, 注意目标对象自身也会改变。 合并具有相同属性的对象 var o1 = { a: 1, b: 1, c: 1 }; var o2 = { b: 2, c: 2 }; var o3 = { c: 3 }; var obj = Object.assign({}, o1, o2, o3); console.log(obj); // { a: 1, b: 2, c: 3 } 六、Map和Set4 Map和Set都叫做集合,但是他们也有所不同。Set常被用来检查对象中是否存在某个键名,Map集合常被用来获取已存的信息。 Set是有序列表,含有相互独立的非重复值。 Array和Set对比 都是一个存储多值的容器,两者可以互相转换,但是在使用场景上有区别。如下: Array的indexOf方法比Set的has方法效率低下 Set不含有重复值(可以利用这个特性实现对一个数组的去重) Set通过delete方法删除某个值,而Array只能通过splice。两者的使用方便程度前者更优 Array的很多新方法map、filter、some、every等是Set没有的(但是通过两者可以互相转换来使用) Object和Map对比 Object是字符串-值,Map是值-值 Object键为string类型,Map的键是任意类型 手动计算Object尺寸,Map.size可以获取尺寸 Map的排序是插入顺序 Object有原型,所以映射中有一些缺省的键。可以理解为Map=Object.create(null) Set操作集合 let set = new Set() // Set转化为数组 let arr = Array.from(set) let arr = [...set] // 实例属性(继承自Set) set.constructor === Set set.size // 操作方法 set.add(1) // 添加一个值 set.delete(1) //删除一个值 set.has(1) //判断是否有这个值(Array中的indexOf) set.clear() //清除所有值 // 获取用于遍历的成员方法(Set的遍历顺序就是插入顺序) set.keys() // 返回键名的遍历器 set.values() // 返回键值得遍历器 set.entries() // 返回键值对的遍历器 set.forEach() // 循环遍历每个值(和Array的方法一致) for (let key of set.keys()){} for (let val of set.values()){} for (let entry of set.entries()){} // 使用数组方法来处理set值 set = new Set(arr) set = new Set([...set].map((x) => x = x * 2)) set = new Set([...set].filter((x) => x > 2)) 前端全栈学习交流圈:866109386,面向1-3经验年前端开发人员,帮助突破技术瓶颈,提升思维能力,群内有大量PDF可供自取,更有干货实战项目视频进群免费领取。 Map的方法集合 let map = new Map() // 实例属性(继承自Map) map.constructor === Map map.size // 操作方法 map.set(1,2) map.get(1) map.delete(1) map.has(1) map.clear() // 遍历方法 map.keys() map.values() map.entries() map.forEach() // Map和数组的转换 map = new Map([['key','val'],[2,1]]) // 要求双成员数组 let arr = [...map] // 值得注意的是Map的键是跟内存绑定的 map.set([1], 's') map.get([1]) let arr = [1] let arr1 = [1] map.set(arr, 's') map.get(arr) map.set(arr1, 's') map.get(arr1) 七、迭代器(Iterator) 1、entries() 返回迭代器:返回键值对 //数组 const arr = ['a', 'b', 'c']; for(let v of arr.entries()) { console.log(v) } // [0, 'a'] [1, 'b'] [2, 'c'] //Set const arr = new Set(['a', 'b', 'c']); for(let v of arr.entries()) { console.log(v) } // ['a', 'a'] ['b', 'b'] ['c', 'c'] //Map const arr = new Map(); arr.set('a', 'a'); arr.set('b', 'b'); for(let v of arr.entries()) { console.log(v) } // ['a', 'a'] ['b', 'b'] 2、values() 返回迭代器:返回键值对的value //数组 const arr = ['a', 'b', 'c']; for(let v of arr.values()) { console.log(v) } //'a' 'b' 'c' //Set const arr = new Set(['a', 'b', 'c']); for(let v of arr.values()) { console.log(v) } // 'a' 'b' 'c' //Map const arr = new Map(); arr.set('a', 'a'); arr.set('b', 'b'); for(let v of arr.values()) { console.log(v) } // 'a' 'b' 3、keys() 返回迭代器:返回键值对的key //数组 const arr = ['a', 'b', 'c']; for(let v of arr.keys()) { console.log(v) } // 0 1 2 //Set const arr = new Set(['a', 'b', 'c']); for(let v of arr.keys()) { console.log(v) } // 'a' 'b' 'c' //Map const arr = new Map(); arr.set('a', 'a'); arr.set('b', 'b'); for(let v of arr.keys()) { console.log(v) } // 'a' 'b' 虽然上面列举了3种内建的迭代器方法,但是不同集合的类型还有自己默认的迭代器,在for of中,数组和Set的默认迭代器是values(),Map的默认迭代器是entries()。 for of循环解构 对象本身不支持迭代,但是我们可以自己添加一个生成器,返回一个key,value的迭代器,然后使用for of循环解构key和value。 const obj = { a: 1, b: 2, *[Symbol.iterator]() { for(let i in obj) { yield [i, obj[i]] } } } for(let [key, value] of obj) { console.log(key, value) } // 'a' 1, 'b' 2 字符串迭代器 const str = 'abc'; for(let v of str) { console.log(v) } // 'a' 'b' 'c' 前端全栈学习交流圈:866109386,面向1-3经验年前端开发人员,帮助突破技术瓶颈,提升思维能力,群内有大量PDF可供自取,更有干货实战项目视频进群免费领取。 ES6给数组添加了几个新方法:find()、findIndex()、fill()、copyWithin() 1、find():传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它,并且终止搜索。 const arr = [1, "2", 3, 3, "2"] console.log(arr.find(n => typeof n === "number")) // 1 2、findIndex():传入一个回调函数,找到数组中符合当前搜索规则的第一个元素,返回它的下标,终止搜索。 const arr = [1, "2", 3, 3, "2"] console.log(arr.findIndex(n => typeof n === "number")) // 0 3、fill():用新元素替换掉数组内的元素,可以指定替换下标范围。 arr.fill(value, start, end) 4、copyWithin():选择数组的某个下标,从该位置开始复制数组元素,默认从0开始复制。也可以指定要复制的元素范围。 arr.copyWithin(target, start, end) const arr = [1, 2, 3, 4, 5] console.log(arr.copyWithin(3)) // [1,2,3,1,2] 从下标为3的元素开始,复制数组,所以4, 5被替换成1, 2 const arr1 = [1, 2, 3, 4, 5] console.log(arr1.copyWithin(3, 1)) // [1,2,3,2,3] 从下标为3的元素开始,复制数组,指定复制的第一个元素下标为1,所以4, 5被替换成2, 3 const arr2 = [1, 2, 3, 4, 5] console.log(arr2.copyWithin(3, 1, 2)) // [1,2,3,2,5] 从下标为3的元素开始,复制数组,指定复制的第一个元素下标为1,结束位置为2,所以4被替换成2 ES6中类class、Promise与异步编程、代理(Proxy)和反射(Reflection)API,这几块内容比较复杂,下次有机会再写

优秀的个人博客,低调大师

Go语言基础语法(package)-5

章节 package 作用及定义 1.package 作用及定义 package 是 最基本的分发单位 和 工程管理中依赖关系 的体现 每个GO语言源代码文件开头都拥有一个package声明,表示源码文件所属的代码包 要生成GO语言可执行程序,必须要有main的package包,且必须在该包下有main()函数 同一个路径下只能存在一个package,一个package可以拆成多个源文件组成 2.实践-验证上述package作用及定义 2.1 必须要有 package main 包 且 必须在该包下有main函数 package 包名不为main package main1 import "fmt" func main1() { fmt.Println("测试") } 运行结果如下图所示: package name->main1 2.2 package main 包中没有main函数-func main1() {...} package main1 import "fmt" func main1() { fmt.Println("测试") } 运行结果如下所示: func name->main1 所以package main 下面需要有main函数,否则生成不了可执行文件 2.3 package 名不为main //package 包名与文件夹名要求保持一致 package learn02 import "fmt" func test02() { fmt.Println("i am test") } package learn02 与 项目文件夹结构目录保持一致 package learn02 与 项目文件夹结构目录保持一致 test02.go 源代码编译情况如下所示: 源代码文件可正常编译 test02.go 源文件可正常编译 2.4 同一个路径下只能存在一个package,一个package可以拆成多个源文件组成 目录结构下新建 main02.go 同一个路径下只存在一个package 运行情况 go build main02.go main02.go 可正常编译、但未生成可执行文件 2.5 一个package可以包含多个源文件 项目目录结构如下所示: 一个package可以包含多个源文件

优秀的个人博客,低调大师

objective-c优雅的语法

对初学者来说,objective-c存在了很多令人费解的写法,实际上他们是非常优雅的。 程序员写的最多的就是函数以及调用自己写的或者别人写的函数。本文就从函数的角度来看下objective-c的优雅之处。 C#和objective-c同属于c系列语言。让我们先看下C#的函数定义和调用,做个对比。 C#函数的定义: public void doIt( string actorName, string movieName, int timesSeen) { Console.Write( " {0} is my favorite actor in the movie {1}, I saw it {2} times. " , actorName, movieName, timesSeen); } 函数的调用: Class1 objMovie = new Class1(); objMovie.doIt( " 莱昂纳多·迪卡普里奥 " , " 盗梦空间 " , 120 ); 在让.net程序员看下objective-c的定义: - ( void ) doIt:(NSString * ) actorName movieName: (NSString * ) value timesSeen: ( int )times { NSLog( @" %@ is my favorite actor in the movie %@, I saw it %i times. " ,actorName, value, times); } 如果你第一次看objective-c,肯定会琢磨不透上面的代码,怀疑是不是写错了。 对于上面这个函数的定义: 1、'-'表示这个函数是实例函数(类似非静态函数),'+'表示这个函数是类函数(类似静态函数) 2、(void)表示这个函数没有返回值。 3、函数名是'doIt: movieName: timesSeen:',而不是'doIt' 4、参数用空格隔开 5、参数类型写在括号中 6、参数分内部参数和外部参数,如电影名称,内部参数是:value,外部参数是:movieName 7、函数的一个参数没有外部参数的名称,有内部参数名。如:actorName。 调用: 从上面代码可以看出除了第一个参数,其余的参数都可以加上外部参数名称用于区别。 从上面可以看出objective-c和C#区别很大,实在会令.net程序员费解。objective-c函数设计的优雅之处在于即有内部参数名又有外部参数名,可以不用在再内部定义变量来存放函数的参数。 总结:本文是对objective-c函数学习的一个简单总结。 本文转自麒麟博客园博客,原文链接:http://www.cnblogs.com/zhuqil/archive/2011/03/15/1984642.html,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android .mk文件语法规范

一个简单的"hello world"的例子,比如,下面的文件: sources/helloworld/helloworld.c sources/helloworld/Android.mk 'helloworld.c'是一个JNI共享库,实现返回"hello world"字符串的原生方法。 相应的Android.mk文件会像下面这样: LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE:= helloworld LOCAL_SRC_FILES := helloworld.c include $(BUILD_SHARED_LIBRARY) 解释一下这几行代码: LOCAL_PATH := $(call my-dir) 一个Android.mk file首先必须定义好LOCAL_PATH变量。它用于在开发树中查找源文件。在这个例子中,宏函数’my-dir’, 由编译系统提供,用于返回当前路径(即包含Android.mk file文件的目录)。 include $( CLEAR_VARS) CLEAR_VARS由编译系统提供,指定让GNU MAKEFILE为你清除许多LOCAL_XXX变量(例如 LOCAL_MODULE, LOCAL_SRC_FILES, LOCAL_STATIC_LIBRARIES, 等等...),除LOCAL_PATH 。这是必要的,因为所有的编译控制文件都在同一个GNU MAKE执行环境中,所有的变量都是全局的。 LOCAL_MODULE := helloworld LOCAL_MODULE变量必须定义,以标识你在Android.mk文件中描述的每个模块。名称必须是唯一的,而且不包含任何空格。注意编译系统会自动产生合适的前缀和后缀,换句话说,一个被命名为'foo'的共享库模块,将会生成'libfoo.so'文件。 **重要注意事项: ** 如果你把库命名为‘libhelloworld’,编译系统将不会添加任何的lib前缀,也会生成libhelloworld.so,这是为了支持来源于Android平台的源代码的Android.mk文件,如果你确实需要这么做的话。 LOCAL_SRC_FILES := helloworld.c LOCAL_SRC_FILES变量必须包含将要编译打包进模块中的C或C++源代码文件。注意,你不用在这里列出头文件和包含文件,因为编译系统将会自动为你找出依赖型的文件;仅仅列出直接传递给编译器的源代码文件就好。【注意,默认的C++源码文件的扩展名是’.cpp’. 指定一个不同的扩展名也是可能的,只要定义LOCAL_DEFAULT_CPP_EXTENSION变量,不要忘记开始的小圆点(也就是定义为‘.cxx’,而不是‘cxx’)(当然这一步我们一般不会去改它)】 include $(BUILD_SHARED_LIBRARY) BUILD_SHARED_LIBRARY是编译系统提供的变量,指向一个GNU Makefile脚本(应该就是在build/core目录下的shared_library.mk),负责收集自从上次调用'include $(CLEAR_VARS)'以来,定义在LOCAL_XXX变量中的所有信息,并且决定编译什么,如何正确地去做。并根据其规则生成静态库。同理对于静态库。 #LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := swscale LOCAL_SRC_FILES := libswscale-4.so include $(PREBUILT_SHARED_LIBRARY) #LOCAL_PATH := $(call my-dir) $(warning "the value of LOCAL_PATH is$(LOCAL_PATH)") include $(CLEAR_VARS) LOCAL_MODULE := FfmpegCmd LOCAL_SRC_FILES := com_mazaiting_ffmpegcmdtest_FFmpegCmd.c ffmpeg.c ffmpeg_opt.c cmdutils.c ffmpeg_filter.c LOCAL_C_INCLUDES := F:/test/FFmpegCmdTest/ffmpeg-3.3.6-1 LOCAL_LDLIBS := -llog -lz -ldl LOCAL_SHARED_LIBRARIES := avcodec avdevice avfilter avformat avutil swresample swscale include $(BUILD_SHARED_LIBRARY) 解释: LOCAL_MODULE 本地模块 LOCAL_SRC_FILES 本地源文件 PREBUILT_SHARED_LIBRARY 指已经编译好的动态库 (LOCAL_PATH)") 可以打印出LOCAL_PATH这个变量 LOCAL_C_INCLUDES 本地源码路径 LOCAL_LDLIBS 指定库或至少要链接到结果中的对象 LOCAL_SHARED_LIBRARIES 编译好的动态库,为此模块中的依赖

优秀的个人博客,低调大师

Swift语法专题五——集合类型

Swift讲解专题五——集合类型 一、引言 Swift中提供了3种集合类型,Array数据类型,Set集合类型,Dictionary字典类型。Array用于存放一组有序的数据,数据角标从0开始一次递增;Set用于存放一组无序的数据,数据不可以重复;Dictionary也用于存放一组无序的数据,只是其是按照键值对的方式存储,键值必须唯一。这里借用官方文档中的一张图来表示3种集合类型的特点: 二、Array类型 Array通常也被称为数组,Swift是一种类型安全语言,其中的Array类型也必须确定其元素的类型,声明数组类型有两种方法,示例如下: //将数组声明为Int类型值集合的数组 var array1:[Int] var array2:Array<Int> //创建空数组 array1 = [] array2 = Array() 数组对象如果通过var变量也接收,则其为可变的数组,可以通过append方法来追加元素,示例如下: //向数组中追加元素 array1.append(3) 在创建数组时,也可以对数组进行初始化,示例如下: //创建数组[0,0,0] var array3 = [Double](count: 3, repeatedValue: 0) //创建数组[2.5,2.5,2.5] var array4 = Array(count: 3, repeatedValue: 2.5) //数组可以使用+号直接进行追加 [0,0,0,2.5,2.5,2.5] var array5 = array3+array4 Swift中提供了许多访问和修改数组的方法,示例代码如下: //获取数组中元素个数 array5.count //判断数组是否为空 array5.isEmpty //通过下标访问数组中的元素 array5[1] //通过下标修改数组元素 array5[1]=2 //修改数据中的一组数据 array5[0...3] = [1,1,1,1] //向数组中某个位置插入一个数据 array5.insert(3, atIndex: 1) //移除数组某个角标处的元素 array5.removeAtIndex(1) //移除数组的最后一个元素 array5.removeLast() //移除数组第一个元素 array5.removeFirst() //遍历整个数组 for item in array5 { print(item) } //遍历数组枚举 for (index,item) in array5.enumerate() { print(index,item) } 三、Set类型 Set类型集合不关注元素的顺序,但是其可以保证其中元素的唯一性。和Array类型一样,Set类型来声明时也需要确定其内元素的类型,示例如下: var set1:Set<Character> = ["a","b","c","d"] 下面示例代码演示对集合进行操作: var set1:Set<Character> = ["a","b","c","d"] var set2:Set<Character> = ["e","f","g"] //向集合中插入元素 set1.insert("z") //获取集合中元素个数 set1.count //判断集合是否为空 set1.isEmpty //将集合中的某个元素移除 set1.remove("a") //移除集合中的所有元素 set1.removeAll() //判断集合中是否包含某个元素 set2.contains("e") //遍历集合 for item in set2 { print(item) } //进行从小到大的排序遍历 for item in set2.sort() { print(item) } Set也支持进行一些集合的数学运算,例如交集,并集,补集等,下面一张图演示了Set进行集合运算的一些特性: intersect()方法返回两个集合的交集。 exclusiveOr()方法用于返回两个集合交集的补集。 union()方法用于返回两个集合的并集。 subtract()方法用于返回第二个集合的补集。 示例代码如下: var set3:Set<Int> = [1,2,3,4] var set4:Set<Int> = [1,2,5,6] //返回交集 {1,2} var setInter = set3.intersect(set4) //返回交集的补集{3,4,5,6} var setEx = set3.exclusiveOr(set4) //返回并集{1,2,3,4,5,6} var setUni = set3.union(set4) //返回第二个集合的补集{3,4} var setSub = set3.subtract(set4) 使用比较运算符==可以比较两个Set集合是否相等,当两个Set集合中所有元素都相等时,这两个集合才相等。下面代码显示了与子集相关的运算: var set5:Set = [1,2] var set6:Set = [2,3] var set7:Set = [1,2,3] var set8:Set = [1,2,3] //判断是否是某个集合的子集 set5是set7的子集 返回ture set5.isSubsetOf(set7) //判断是否是某个集合的超集 set7是set5的超集 返回ture set7.isSupersetOf(set5) //判断是否是某个集合的真子集 set5是set7的真子集 返回ture set5.isStrictSubsetOf(set7) //判断是否是某个集合的真超集 set7不是set8的真超集 返回false set7.isStrictSupersetOf(set8) 四、Dictionary类型 Swift中的Dictionary在声明时必须明确键的类型和值的类型,示例如下: var dic:Dictionary<Int,String> var dic2:[Int:String] = [1:"one",2:"Two"] 访问与操作Dictionary的方法,代码示例如下: var dic2:[Int:String] = [1:"One",2:"Two",3:"Three",4:"Four"] //获取字典键值对个数 dic2.count //判断字典是否为空 dic2.isEmpty //通过键获取值 dic2[1] //通过键修改值 dic2[1] = "First" //添加键值 dic2[0] = "Zero" //updateValue 方法将更新一个键值 如果此键存在 则更新键值 并且将旧的键值返回 如果此键不存在 则添加键值 返回nil 其返回的为一个Optional类型值 可以使用if let进行处理 dic2.updateValue("9", forKey: 1) //使用if let 处理updateValue的返回值 if let oldValue = dic2.updateValue("One", forKey: 1) { print("Old Value is \(oldValue)") } //通过键值获取的数据也将是有个Optional类型的值 也可以使用if let if let value = dic2[1] { print("The Value is \(value)") } //移除某个键值对 dic2[9]=nil dic2.removeValueForKey(9) //对字典进行遍历 for (key,value) in dic2 { print(key,value) } //遍历所有键 for key in dic2.keys { print(key) } //遍历所有值 for value in dic2.values { print(value) } //进行从小到大的排序遍历 for key in dic2.keys.sort() { print(key) }

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册